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)
+})()