diff --git a/index.html b/index.html index a31cedf..8e16960 100644 --- a/index.html +++ b/index.html @@ -45,6 +45,8 @@ + + diff --git a/notebooks/convolutional/Convolution1D.ipynb b/notebooks/convolutional/Convolution1D.ipynb new file mode 100644 index 0000000..dbe3d9b --- /dev/null +++ b/notebooks/convolutional/Convolution1D.ipynb @@ -0,0 +1,304 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Using TensorFlow backend.\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "from keras.models import Model\n", + "from keras.layers import Input\n", + "from keras.layers.convolutional import Convolution1D\n", + "from keras import backend as K" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def format_decimal(arr, places=6):\n", + " return [round(x * 10**places) / 10**places for x in arr]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Convolution1D" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**[convolutional.Convolution1D.0] 4 length 3 filters on 5x2 input, activation='linear', border_mode='valid', subsample_length=1, bias=True**" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "W shape: (4, 2, 3, 1)\n", + "W: [0.895265, -0.546905, 0.18884, -0.143383, 0.528281, -0.994279, -0.285153, 0.81939, -0.087838, 0.963605, 0.734714, 0.972055, 0.846533, -0.392613, 0.692207, -0.757556, 0.571153, -0.49899, -0.807941, 0.886982, 0.6521, 0.03665, 0.747001, 0.156751]\n", + "b shape: (4,)\n", + "b: [0.895265, -0.546905, 0.18884, -0.143383]\n", + "\n", + "in shape: (5, 2)\n", + "in: [0.528281, -0.994279, -0.285153, 0.81939, -0.087838, 0.963605, 0.734714, 0.972055, 0.846533, -0.392613]\n", + "out shape: (3, 4)\n", + "out: [1.124918, -0.342879, 1.42759, -0.153716, 0.251835, 1.840331, -0.064904, 1.390416, 1.340388, 1.266877, 0.433117, 1.831188]\n" + ] + } + ], + "source": [ + "data_in_shape = (5, 2)\n", + "conv = Convolution1D(4, 3, activation='linear', border_mode='valid', subsample_length=1, bias=True)\n", + "\n", + "layer_0 = Input(shape=data_in_shape)\n", + "layer_1 = conv(layer_0)\n", + "model = Model(input=layer_0, output=layer_1)\n", + "\n", + "# set weights to random (use seed for reproducibility)\n", + "weights = []\n", + "for w in model.get_weights():\n", + " np.random.seed(200)\n", + " weights.append(2 * np.random.random(w.shape) - 1)\n", + "model.set_weights(weights)\n", + "print('W shape:', weights[0].shape)\n", + "print('W:', format_decimal(weights[0].ravel().tolist()))\n", + "print('b shape:', weights[1].shape)\n", + "print('b:', format_decimal(weights[1].ravel().tolist()))\n", + "\n", + "data_in = 2 * np.random.random(data_in_shape) - 1\n", + "print('')\n", + "print('in shape:', data_in_shape)\n", + "print('in:', format_decimal(data_in.ravel().tolist()))\n", + "result = model.predict(np.array([data_in]))\n", + "print('out shape:', result[0].shape)\n", + "print('out:', format_decimal(result[0].ravel().tolist()))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**[convolutional.Convolution1D.1] 4 length 3 filters on 6x3 input, activation='linear', border_mode='valid', subsample_length=1, bias=False**" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "W shape: (4, 3, 3, 1)\n", + "W: [-0.772191, 0.495762, 0.222119, 0.619384, 0.425715, -0.719926, -0.464976, -0.704791, -0.543864, -0.528877, 0.380048, -0.703304, -0.108788, 0.401685, -0.806723, 0.765265, 0.739665, 0.689188, -0.452596, 0.571359, 0.402272, -0.010539, 0.672675, -0.191632, -0.653554, -0.269196, 0.994178, -0.318691, 0.010759, 0.078695, -0.501326, 0.625487, -0.614715, -0.839499, -0.811676, -0.300069]\n", + "\n", + "in shape: (6, 3)\n", + "in: [0.57107, 0.361384, -0.924121, -0.417132, -0.39254, 0.967698, -0.674584, 0.924125, 0.403362, 0.417301, 0.795356, -0.367641, -0.398474, 0.889135, -0.81216, 0.383587, 0.922044, 0.427167]\n", + "out shape: (4, 4)\n", + "out: [-1.877892, -0.642056, -0.468639, -1.365037, -0.876249, 0.22855, -0.661939, -0.585044, 1.423414, -0.225706, -0.233745, -0.120764, 0.283789, -1.702796, 1.034372, 0.323189]\n" + ] + } + ], + "source": [ + "data_in_shape = (6, 3)\n", + "conv = Convolution1D(4, 3, activation='linear', border_mode='valid', subsample_length=1, bias=False)\n", + "\n", + "layer_0 = Input(shape=data_in_shape)\n", + "layer_1 = conv(layer_0)\n", + "model = Model(input=layer_0, output=layer_1)\n", + "\n", + "# set weights to random (use seed for reproducibility)\n", + "weights = []\n", + "for w in model.get_weights():\n", + " np.random.seed(201)\n", + " weights.append(2 * np.random.random(w.shape) - 1)\n", + "model.set_weights(weights)\n", + "print('W shape:', weights[0].shape)\n", + "print('W:', format_decimal(weights[0].ravel().tolist()))\n", + "\n", + "data_in = 2 * np.random.random(data_in_shape) - 1\n", + "print('')\n", + "print('in shape:', data_in_shape)\n", + "print('in:', format_decimal(data_in.ravel().tolist()))\n", + "result = model.predict(np.array([data_in]))\n", + "print('out shape:', result[0].shape)\n", + "print('out:', format_decimal(result[0].ravel().tolist()))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**[convolutional.Convolution1D.2] 2 length 3 filters on 4x6 input, activation='sigmoid', border_mode='valid', subsample_length=2, bias=True**" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "W shape: (2, 6, 3, 1)\n", + "W: [0.895265, -0.546905, 0.18884, -0.143383, 0.528281, -0.994279, -0.285153, 0.81939, -0.087838, 0.963605, 0.734714, 0.972055, 0.846533, -0.392613, 0.692207, -0.757556, 0.571153, -0.49899, -0.807941, 0.886982, 0.6521, 0.03665, 0.747001, 0.156751, -0.099831, 0.360314, -0.161149, 0.280787, 0.217313, -0.789132, 0.932089, 0.517401, 0.359284, -0.341304, -0.94709, 0.607321]\n", + "b shape: (2,)\n", + "b: [0.895265, -0.546905]\n", + "\n", + "in shape: (4, 6)\n", + "in: [0.18884, -0.143383, 0.528281, -0.994279, -0.285153, 0.81939, -0.087838, 0.963605, 0.734714, 0.972055, 0.846533, -0.392613, 0.692207, -0.757556, 0.571153, -0.49899, -0.807941, 0.886982, 0.6521, 0.03665, 0.747001, 0.156751, -0.099831, 0.360314]\n", + "out shape: (2, 2)\n", + "out: [0.444624, 0.773535, 0.564385, 0.133453]\n" + ] + } + ], + "source": [ + "data_in_shape = (4, 6)\n", + "conv = Convolution1D(2, 3, activation='sigmoid', border_mode='same', subsample_length=2, bias=True)\n", + "\n", + "layer_0 = Input(shape=data_in_shape)\n", + "layer_1 = conv(layer_0)\n", + "model = Model(input=layer_0, output=layer_1)\n", + "\n", + "# set weights to random (use seed for reproducibility)\n", + "weights = []\n", + "for w in model.get_weights():\n", + " np.random.seed(200)\n", + " weights.append(2 * np.random.random(w.shape) - 1)\n", + "model.set_weights(weights)\n", + "print('W shape:', weights[0].shape)\n", + "print('W:', format_decimal(weights[0].ravel().tolist()))\n", + "print('b shape:', weights[1].shape)\n", + "print('b:', format_decimal(weights[1].ravel().tolist()))\n", + "\n", + "data_in = 2 * np.random.random(data_in_shape) - 1\n", + "print('')\n", + "print('in shape:', data_in_shape)\n", + "print('in:', format_decimal(data_in.ravel().tolist()))\n", + "result = model.predict(np.array([data_in]))\n", + "print('out shape:', result[0].shape)\n", + "print('out:', format_decimal(result[0].ravel().tolist()))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**[convolutional.Convolution1D.4] 2 length 7 filters on 8x3 input, activation='tanh', border_mode='same', subsample_length=1, bias=True**" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "W shape: (2, 3, 7, 1)\n", + "W: [0.861113, -0.237594, 0.330694, 0.998309, 0.786447, 0.538158, -0.228315, 0.217332, -0.475436, -0.018066, -0.489741, -0.522387, 0.79989, 0.27058, -0.683115, -0.650208, 0.259853, -0.509243, 0.958185, 0.089546, 0.739799, 0.114385, -0.378872, -0.168716, 0.302124, 0.850416, -0.984343, 0.839927, -0.895196, 0.303711, 0.128826, 0.058159, 0.254989, -0.759101, 0.793844, 0.647309, 0.252074, 0.075576, -0.859305, 0.952613, -0.053285, -0.677361]\n", + "b shape: (2,)\n", + "b: [0.861113, -0.237594]\n", + "\n", + "in shape: (8, 3)\n", + "in: [0.330694, 0.998309, 0.786447, 0.538158, -0.228315, 0.217332, -0.475436, -0.018066, -0.489741, -0.522387, 0.79989, 0.27058, -0.683115, -0.650208, 0.259853, -0.509243, 0.958185, 0.089546, 0.739799, 0.114385, -0.378872, -0.168716, 0.302124, 0.850416]\n", + "out shape: (8, 2)\n", + "out: [0.854959, 0.355561, 0.888899, -0.9834, -0.825345, 0.941694, -0.490434, -0.699848, 0.872523, -0.887886, -0.408331, -0.018902, 0.959544, 0.66633, -0.779488, 0.038157]\n" + ] + } + ], + "source": [ + "data_in_shape = (8, 3)\n", + "conv = Convolution1D(2, 7, activation='tanh', border_mode='same', subsample_length=1, bias=True)\n", + "\n", + "layer_0 = Input(shape=data_in_shape)\n", + "layer_1 = conv(layer_0)\n", + "model = Model(input=layer_0, output=layer_1)\n", + "\n", + "# set weights to random (use seed for reproducibility)\n", + "weights = []\n", + "for w in model.get_weights():\n", + " np.random.seed(204)\n", + " weights.append(2 * np.random.random(w.shape) - 1)\n", + "model.set_weights(weights)\n", + "print('W shape:', weights[0].shape)\n", + "print('W:', format_decimal(weights[0].ravel().tolist()))\n", + "print('b shape:', weights[1].shape)\n", + "print('b:', format_decimal(weights[1].ravel().tolist()))\n", + "\n", + "data_in = 2 * np.random.random(data_in_shape) - 1\n", + "print('')\n", + "print('in shape:', data_in_shape)\n", + "print('in:', format_decimal(data_in.ravel().tolist()))\n", + "result = model.predict(np.array([data_in]))\n", + "print('out shape:', result[0].shape)\n", + "print('out:', format_decimal(result[0].ravel().tolist()))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/src/Tensor.js b/src/Tensor.js index 7af2f52..fbc9968 100644 --- a/src/Tensor.js +++ b/src/Tensor.js @@ -47,7 +47,7 @@ export default class Tensor { * 2-D only * see https://github.com/waylonflinn/weblas/wiki/Pipeline */ - createWeblasTensor = () => { + createWeblasTensor () { if (this.tensor.shape.length === 1) { const shape = [1, this.tensor.shape[0]] this.weblasTensor = new weblas.pipeline.Tensor(shape, this.tensor.data) @@ -60,7 +60,7 @@ export default class Tensor { /** * Transfers weblas pipeline tensor from GPU memory */ - transferWeblasTensor = () => { + transferWeblasTensor () { if (this.weblasTensor) { const shape = this.weblasTensor.shape const arr = this.weblasTensor.transfer(true) @@ -71,7 +71,7 @@ export default class Tensor { /** * Delete weblas pipeline tensor */ - deleteWeblasTensor = () => { + deleteWeblasTensor () { if (this.weblasTensor) { this.weblasTensor.delete() delete this.weblasTensor @@ -81,7 +81,7 @@ export default class Tensor { /** * Replaces data in the underlying ndarray. */ - replaceTensorData = data => { + replaceTensorData (data) { if (data && data.length && data instanceof this._type) { this.tensor.data = data } else if (data && data.length && data instanceof Array) { diff --git a/src/engine/Layer.js b/src/engine/Layer.js index 940d83f..ff74075 100644 --- a/src/engine/Layer.js +++ b/src/engine/Layer.js @@ -27,7 +27,7 @@ export default class Layer { * * @param {Tensor[]} weightsArr - array of weights which are instances of Tensor */ - setWeights = weightsArr => { + setWeights (weightsArr) { this.params.forEach((p, i) => { this.weights[p] = weightsArr[i] }) @@ -37,7 +37,7 @@ export default class Layer { * Create weblas pipeline tensor weights * 2-D only */ - createWeblasWeights = () => { + createWeblasWeights () { this.weblasWeights = {} this.params.forEach((p, i) => { @@ -54,7 +54,7 @@ export default class Layer { /** * Transfer weblas pipeline tensor weights */ - transferWeblasWeights = () => { + transferWeblasWeights () { this.params.forEach((p, i) => { if (this.weblasWeights[p]) { const shape = this.weblasWeights[p].shape @@ -67,7 +67,7 @@ export default class Layer { /** * Delete weblas pipeline tensor weights */ - deleteWeblasWeights = () => { + deleteWeblasWeights () { this.params.forEach((p, i) => { if (this.weblasWeights[p]) { this.weblasWeights[p].delete() diff --git a/src/layers/convolutional/Convolution1D.js b/src/layers/convolutional/Convolution1D.js new file mode 100644 index 0000000..5d1bfeb --- /dev/null +++ b/src/layers/convolutional/Convolution1D.js @@ -0,0 +1,65 @@ +import Layer from '../../engine/Layer' +import Convolution2D from './Convolution2D' +import squeeze from 'ndarray-squeeze' +import unsqueeze from 'ndarray-unsqueeze' + +/** + * Convolution1D layer class + */ +export default class Convolution1D extends Layer { + /** + * Creates a Convolution1D layer + * @param {number} nbFilter - Number of convolution filters to use. + * @param {number} filterLength - Length of 1D convolution kernel. + * @param {Object} [attrs] - layer attributes + */ + constructor (nbFilter, filterLength, attrs = {}) { + super(attrs) + const { + activation = 'linear', + borderMode = 'valid', + subsampleLength = 1, + bias = true + } = attrs + + if (borderMode !== 'valid' && borderMode !== 'same') { + throw new Error(`${this.name} [Convolution1D layer] Invalid borderMode.`) + } + + // Layer weights specification + this.params = this.bias ? ['W', 'b'] : ['W'] + + // Bootstrap Convolution2D layer: + // Convolution1D is actually a shim on top of Convolution2D, where + // all of the computational action is performed + // Note that Keras uses `th` dim ordering here. + this._conv2d = new Convolution2D(nbFilter, filterLength, 1, { + activation, + borderMode, + subsample: [subsampleLength, 1], + dimOrdering: 'th', + bias + }) + } + + /** + * Method for setting layer weights + * Override `super` method since weights must be set in `this._conv2d` + * @param {Tensor[]} weightsArr - array of weights which are instances of Tensor + */ + setWeights (weightsArr) { + this._conv2d.setWeights(weightsArr) + } + + /** + * Method for layer computational logic + * @param {Tensor} x + * @returns {Tensor} x + */ + call = x => { + x.tensor = unsqueeze(x.tensor).transpose(1, 0, 2) + const conv2dOutput = this._conv2d.call(x) + x.tensor = squeeze(conv2dOutput.tensor).transpose(1, 0, 2) + return x + } +} diff --git a/src/layers/convolutional/Convolution2D.js b/src/layers/convolutional/Convolution2D.js index 5b82102..df0439e 100644 --- a/src/layers/convolutional/Convolution2D.js +++ b/src/layers/convolutional/Convolution2D.js @@ -58,16 +58,12 @@ export default class Convolution2D extends Layer { * In `th` mode, W weight tensor has shape [nbFilter, inputChannels, nbRow, nbCol] * @param {Tensor[]} weightsArr - array of weights which are instances of Tensor */ - setWeights = weightsArr => { + setWeights (weightsArr) { if (this.dimOrdering === 'th') { - const weightsArrTheano = weightsArr.map(w => { - w.tensor = w.tensor.transpose(3, 2, 0, 1) - return w - }) - super.setWeights(weightsArrTheano) - } else { - super.setWeights(weightsArr) + // W + weightsArr[0].tensor = weightsArr[0].tensor.transpose(2, 3, 1, 0) } + super.setWeights(weightsArr) } /** @@ -193,7 +189,7 @@ export default class Convolution2D extends Layer { call = x => { // convert to tf ordering if (this.dimOrdering === 'th') { - x.tensor = x.tensor.transpose(2, 0, 1) + x.tensor = x.tensor.transpose(1, 2, 0) } this._calcOutputShape(x) @@ -239,6 +235,11 @@ export default class Convolution2D extends Layer { this.activation(x) + // convert back to th ordering if necessary + if (this.dimOrdering === 'th') { + x.tensor = x.tensor.transpose(2, 0, 1) + } + return x } } diff --git a/src/layers/convolutional/index.js b/src/layers/convolutional/index.js index 4fa948b..4896e7c 100644 --- a/src/layers/convolutional/index.js +++ b/src/layers/convolutional/index.js @@ -1,5 +1,7 @@ +import Convolution1D from './Convolution1D' import Convolution2D from './Convolution2D' export { + Convolution1D, Convolution2D } diff --git a/src/layers/core/Merge.js b/src/layers/core/Merge.js index f081413..ca78934 100644 --- a/src/layers/core/Merge.js +++ b/src/layers/core/Merge.js @@ -43,7 +43,7 @@ export default class Merge extends Layer { * @param {Tensor[]} inputs * @returns {boolean} valid */ - _validateInputs = inputs => { + _validateInputs (inputs) { const shapes = inputs.map(x => x.tensor.shape.slice()) if (['sum', 'mul', 'ave', 'cos', 'max'].indexOf(this.mode) > -1) { if (!shapes.every(shape => isEqual(shape, shapes[0]))) { diff --git a/test/convolutional/Convolution1D.js b/test/convolutional/Convolution1D.js new file mode 100644 index 0000000..9ab3aae --- /dev/null +++ b/test/convolutional/Convolution1D.js @@ -0,0 +1,105 @@ +/* eslint-env browser, mocha */ + +describe('convolutional layer: Convolution1D', function () { + const assert = chai.assert + const styles = testGlobals.styles + const logTime = testGlobals.logTime + const stringifyCondensed = testGlobals.stringifyCondensed + const approxEquals = KerasJS.testUtils.approxEquals + const layers = KerasJS.layers + + const testParams = [ + { + inputShape: [5, 2], + kernelShape: [4, 3], + attrs: { activation: 'linear', borderMode: 'valid', subsampleLength: 1, bias: true } + }, + { + inputShape: [6, 3], + kernelShape: [4, 3], + attrs: { activation: 'linear', borderMode: 'valid', subsampleLength: 1, bias: false } + }, + { + inputShape: [4, 6], + kernelShape: [2, 3], + attrs: { activation: 'sigmoid', borderMode: 'same', subsampleLength: 2, bias: true } + }, + { + inputShape: [8, 3], + kernelShape: [2, 7], + attrs: { activation: 'tanh', borderMode: 'same', subsampleLength: 1, bias: true } + } + ] + + before(function () { + console.log('\n%cconvolutional layer: Convolution1D', styles.h1) + }) + + /********************************************************* + * CPU + *********************************************************/ + + describe('CPU', function () { + before(function () { + console.log('\n%cCPU', styles.h2) + }) + + testParams.forEach(({ inputShape, kernelShape, attrs }, i) => { + const key = `convolutional.Convolution1D.${i}` + const [inputLength, inputFeatures] = inputShape + const [nbFilter, filterLength] = kernelShape + const title = `[${key}] [CPU] test: ${nbFilter} length ${filterLength} filters on ${inputLength}x${inputFeatures} input, activation='${attrs.activation}', border_mode='${attrs.borderMode}', subsampleLength=${attrs.subsampleLength}, bias=${attrs.bias}` + + it(title, function () { + console.log(`\n%c${title}`, styles.h3) + let testLayer = new layers.Convolution1D(nbFilter, filterLength, attrs) + testLayer.setWeights(TEST_DATA[key].weights.map(w => new KerasJS.Tensor(w.data, w.shape))) + let t = new KerasJS.Tensor(TEST_DATA[key].input.data, TEST_DATA[key].input.shape) + console.log('%cin', styles.h4, stringifyCondensed(t.tensor)) + const startTime = performance.now() + t = testLayer.call(t) + const endTime = performance.now() + console.log('%cout', styles.h4, stringifyCondensed(t.tensor)) + logTime(startTime, endTime) + const dataExpected = new Float32Array(TEST_DATA[key].expected.data) + const shapeExpected = TEST_DATA[key].expected.shape + assert.deepEqual(t.tensor.shape, shapeExpected) + assert.isTrue(approxEquals(t.tensor, dataExpected)) + }) + }) + }) + + /********************************************************* + * GPU + *********************************************************/ + + describe('GPU', function () { + before(function () { + console.log('\n%cGPU', styles.h2) + }) + + testParams.forEach(({ inputShape, kernelShape, attrs }, i) => { + const key = `convolutional.Convolution1D.${i}` + const [inputLength, inputFeatures] = inputShape + const [nbFilter, filterLength] = kernelShape + const title = `[${key}] [GPU] test: ${nbFilter} length ${filterLength} filters on ${inputLength}x${inputFeatures} input, activation='${attrs.activation}', border_mode='${attrs.borderMode}', subsampleLength=${attrs.subsampleLength}, bias=${attrs.bias}` + + it(title, function () { + console.log(`\n%c${title}`, styles.h3) + let testLayer = new layers.Convolution1D(nbFilter, filterLength, attrs) + testLayer.setWeights(TEST_DATA[key].weights.map(w => new KerasJS.Tensor(w.data, w.shape))) + let t = new KerasJS.Tensor(TEST_DATA[key].input.data, TEST_DATA[key].input.shape, { useWeblas: true }) + console.log('%cin', styles.h4, stringifyCondensed(t.tensor)) + const startTime = performance.now() + t = testLayer.call(t) + const endTime = performance.now() + console.log('%cout', styles.h4, stringifyCondensed(t.tensor)) + logTime(startTime, endTime) + const dataExpected = new Float32Array(TEST_DATA[key].expected.data) + const shapeExpected = TEST_DATA[key].expected.shape + assert.deepEqual(t.tensor.shape, shapeExpected) + assert.isTrue(approxEquals(t.tensor, dataExpected)) + }) + }) + }) +}) diff --git a/test/convolutional/Convolution2D.js b/test/convolutional/Convolution2D.js index c4fef0d..308233e 100644 --- a/test/convolutional/Convolution2D.js +++ b/test/convolutional/Convolution2D.js @@ -63,7 +63,7 @@ describe('convolutional layer: Convolution2D', function () { const key = `convolutional.Convolution2D.${i}` const [inputRows, inputCols, inputChannels] = inputShape const [nbFilter, nbRow, nbCol] = kernelShape - const title = `[${key}] [CPU] test 1: ${nbFilter} ${nbRow}x${nbCol} filters on ${inputRows}x${inputCols}x${inputChannels} input, activation='${attrs.activation}', border_mode='${attrs.borderMode}', subsample=${attrs.subsample}, dim_ordering='${attrs.dimOrdering}', bias=${attrs.bias}` + const title = `[${key}] [CPU] test: ${nbFilter} ${nbRow}x${nbCol} filters on ${inputRows}x${inputCols}x${inputChannels} input, activation='${attrs.activation}', border_mode='${attrs.borderMode}', subsample=${attrs.subsample}, dim_ordering='${attrs.dimOrdering}', bias=${attrs.bias}` it(title, function () { console.log(`\n%c${title}`, styles.h3) @@ -97,7 +97,7 @@ describe('convolutional layer: Convolution2D', function () { const key = `convolutional.Convolution2D.${i}` const [inputRows, inputCols, inputChannels] = inputShape const [nbFilter, nbRow, nbCol] = kernelShape - const title = `[${key}] [GPU] test 1: ${nbFilter} ${nbRow}x${nbCol} filters on ${inputRows}x${inputCols}x${inputChannels} input, activation='${attrs.activation}', border_mode='${attrs.borderMode}', subsample=${attrs.subsample}, dim_ordering='${attrs.dimOrdering}', bias=${attrs.bias}` + const title = `[${key}] [GPU] test: ${nbFilter} ${nbRow}x${nbCol} filters on ${inputRows}x${inputCols}x${inputChannels} input, activation='${attrs.activation}', border_mode='${attrs.borderMode}', subsample=${attrs.subsample}, dim_ordering='${attrs.dimOrdering}', bias=${attrs.bias}` it(title, function () { console.log(`\n%c${title}`, styles.h3) diff --git a/test/convolutional/data_Convolution1D.js b/test/convolutional/data_Convolution1D.js new file mode 100644 index 0000000..c97e6d5 --- /dev/null +++ b/test/convolutional/data_Convolution1D.js @@ -0,0 +1,90 @@ +// TEST DATA +// Keyed by mocha test ID +// Python code for generating test data can be found in the matching jupyter notebook in folder `notebooks/`. + +(function () { + var DATA = { + 'convolutional.Convolution1D.0': { + input: { + data: [0.528281, -0.994279, -0.285153, 0.81939, -0.087838, 0.963605, 0.734714, 0.972055, 0.846533, -0.392613], + shape: [5, 2] + }, + weights: [ + { + data: [0.895265, -0.546905, 0.18884, -0.143383, 0.528281, -0.994279, -0.285153, 0.81939, -0.087838, 0.963605, 0.734714, 0.972055, 0.846533, -0.392613, 0.692207, -0.757556, 0.571153, -0.49899, -0.807941, 0.886982, 0.6521, 0.03665, 0.747001, 0.156751], + shape: [4, 2, 3, 1] + }, + { + data: [0.895265, -0.546905, 0.18884, -0.143383], + shape: [4] + } + ], + expected: { + data: [1.124918, -0.342879, 1.42759, -0.153716, 0.251835, 1.840331, -0.064904, 1.390416, 1.340388, 1.266877, 0.433117, 1.831188], + shape: [3, 4] + } + }, + 'convolutional.Convolution1D.1': { + input: { + data: [0.57107, 0.361384, -0.924121, -0.417132, -0.39254, 0.967698, -0.674584, 0.924125, 0.403362, 0.417301, 0.795356, -0.367641, -0.398474, 0.889135, -0.81216, 0.383587, 0.922044, 0.427167], + shape: [6, 3] + }, + weights: [ + { + data: [-0.772191, 0.495762, 0.222119, 0.619384, 0.425715, -0.719926, -0.464976, -0.704791, -0.543864, -0.528877, 0.380048, -0.703304, -0.108788, 0.401685, -0.806723, 0.765265, 0.739665, 0.689188, -0.452596, 0.571359, 0.402272, -0.010539, 0.672675, -0.191632, -0.653554, -0.269196, 0.994178, -0.318691, 0.010759, 0.078695, -0.501326, 0.625487, -0.614715, -0.839499, -0.811676, -0.300069], + shape: [4, 3, 3, 1] + }, + { + data: [0.895265, -0.546905, 0.18884, -0.143383], + shape: [4] + } + ], + expected: { + data: [-1.877892, -0.642056, -0.468639, -1.365037, -0.876249, 0.22855, -0.661939, -0.585044, 1.423414, -0.225706, -0.233745, -0.120764, 0.283789, -1.702796, 1.034372, 0.323189], + shape: [4, 4] + } + }, + 'convolutional.Convolution1D.2': { + input: { + data: [0.18884, -0.143383, 0.528281, -0.994279, -0.285153, 0.81939, -0.087838, 0.963605, 0.734714, 0.972055, 0.846533, -0.392613, 0.692207, -0.757556, 0.571153, -0.49899, -0.807941, 0.886982, 0.6521, 0.03665, 0.747001, 0.156751, -0.099831, 0.360314], + shape: [4, 6] + }, + weights: [ + { + data: [0.895265, -0.546905, 0.18884, -0.143383, 0.528281, -0.994279, -0.285153, 0.81939, -0.087838, 0.963605, 0.734714, 0.972055, 0.846533, -0.392613, 0.692207, -0.757556, 0.571153, -0.49899, -0.807941, 0.886982, 0.6521, 0.03665, 0.747001, 0.156751, -0.099831, 0.360314, -0.161149, 0.280787, 0.217313, -0.789132, 0.932089, 0.517401, 0.359284, -0.341304, -0.94709, 0.607321], + shape: [2, 6, 3, 1] + }, + { + data: [0.895265, -0.546905], + shape: [2] + } + ], + expected: { + data: [0.444624, 0.773535, 0.564385, 0.133453], + shape: [2, 2] + } + }, + 'convolutional.Convolution1D.3': { + input: { + data: [0.330694, 0.998309, 0.786447, 0.538158, -0.228315, 0.217332, -0.475436, -0.018066, -0.489741, -0.522387, 0.79989, 0.27058, -0.683115, -0.650208, 0.259853, -0.509243, 0.958185, 0.089546, 0.739799, 0.114385, -0.378872, -0.168716, 0.302124, 0.850416], + shape: [8, 3] + }, + weights: [ + { + data: [0.861113, -0.237594, 0.330694, 0.998309, 0.786447, 0.538158, -0.228315, 0.217332, -0.475436, -0.018066, -0.489741, -0.522387, 0.79989, 0.27058, -0.683115, -0.650208, 0.259853, -0.509243, 0.958185, 0.089546, 0.739799, 0.114385, -0.378872, -0.168716, 0.302124, 0.850416, -0.984343, 0.839927, -0.895196, 0.303711, 0.128826, 0.058159, 0.254989, -0.759101, 0.793844, 0.647309, 0.252074, 0.075576, -0.859305, 0.952613, -0.053285, -0.677361], + shape: [2, 3, 7, 1] + }, + { + data: [0.861113, -0.237594], + shape: [2] + } + ], + expected: { + data: [0.854959, 0.355561, 0.888899, -0.9834, -0.825345, 0.941694, -0.490434, -0.699848, 0.872523, -0.887886, -0.408331, -0.018902, 0.959544, 0.66633, -0.779488, 0.038157], + shape: [8, 2] + } + } + } + + window.TEST_DATA = Object.assign({}, window.TEST_DATA, DATA) +})()