Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

completed l2pool2d #68

Merged
merged 8 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/pool2d.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import {computePaddingForAutoPad} from './lib/compute-padding.js';
import {Tensor} from './lib/tensor.js';
import {transpose} from './transpose.js';
import {meanReducer, maxReducer} from './reduce.js';
import {l2Reducer, meanReducer, maxReducer} from './reduce.js';
import {validatePool2dParams} from './lib/validate-input.js';

/**
Expand All @@ -26,7 +26,6 @@ function pool2d(input, reductionFunc,
}= {}) {
validatePool2dParams(...arguments);
const roundingFunc = roundingType === 'floor' ? Math.floor : Math.ceil;

if (layout === 'nhwc') {
// nhwc -> nchw
input = transpose(input, {permutation: [0, 3, 1, 2]});
Expand Down Expand Up @@ -125,3 +124,14 @@ export function averagePool2d(input, options = {}) {
export function maxPool2d(input, options = {}) {
return pool2d(input, maxReducer, options);
}

/**
* Compute a L2 reduction operation across all the elements within the moving window over
* the input tensor.
* @param {Tensor} input
* @param {MLPool2dOptions} options
* @return {Tensor}
*/
export function l2Pool2d(input, options = {}) {
return pool2d(input, l2Reducer, options);
}
11 changes: 11 additions & 0 deletions src/reduce.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,17 @@ export function reduceL1(input, options = {}) {
return reduceSum(abs(input), options);
}

/* The l2 reducer */
export function l2Reducer(previousValue, currentValue, currentIndex, array) {
if (currentIndex == 1) {
const sumOfSquares = previousValue * previousValue + currentValue * currentValue;
return sumOfSquares;
} else {
const sumOfSquares = previousValue + currentValue * currentValue;
return (currentIndex === array.length - 1) ? Math.sqrt(sumOfSquares) :sumOfSquares;
}
}

/**
* Compute the L2 norm of all the input values along the axes.
* @param {Tensor} input
Expand Down
285 changes: 285 additions & 0 deletions test/l2pool2d_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
'use strict';
import {l2Pool2d} from '../src/pool2d.js';
import {Tensor} from '../src/lib/tensor.js';

import * as utils from './utils.js';

describe('test pool2d', function() {
it('l2Pool2d default', function() {
const x = new Tensor([1, 1, 4, 4], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
const windowDimensions = [3, 3];
const y = l2Pool2d(x, {windowDimensions});
utils.checkShape(y, [1, 1, 2, 2]);
utils.checkValue(y, [
20.639767440550294,
23.302360395462088,
31.654383582688826,
34.51086785347479,
]);
});

it('l2Pool2d default: a single element test case', function() {
const x = new Tensor([1, 1, 1, 1], [2]);
const windowDimensions = [1, 1];
const y = l2Pool2d(x, {windowDimensions});
utils.checkShape(y, [1, 1, 1, 1]);
utils.checkValue(y, [2]);
});

it('l2Pool2d default: start not from 1 ', function() {
const x = new Tensor([1, 1, 4, 4], [16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]);
const windowDimensions = [3, 3];
const y = l2Pool2d(x, {windowDimensions});
utils.checkShape(y, [1, 1, 2, 2]);
utils.checkValue(y, [
34.51086785347479,
31.654383582688826,
23.302360395462088,
20.639767440550294,
]);
});

it('l2Pool2d nhwc', function() {
const x = new Tensor([1, 4, 4, 1], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
const windowDimensions = [3, 3];
const layout = 'nhwc';
Copy link

@fdwr fdwr Jan 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(update) I found a use case for normalizing across batches and spatial dimensions - this may inform future decisions: https://ieeexplore.ieee.org/document/8648206

🤔 I've often wondered why pooling in ML doesn't have variations that can pool across batches or channel dimensions too, not only the "spatial" dimensions. e.g. One could generalize pooling consistently like reduction (which can apply from all dimensions to none), and pass windowDimensions of rank 4 with [3, 1, 3, 3] to pool along batches too, or just [1, 3, 1, 1] for only channels. Also it would eliminate the then redundant layout parameter. Now, DML can achieve the latter via explicit strides (to map the first dimension to the last dimensions), but there's no way to access it via WebNN, and I've never heard of a need for it anyway. 🤷 I suppose if a researcher really wanted it, they could transpose the batch dimension to the X dimension, pool, then transpose back; and if there has been such a use case, that's probably exactly what they did.

*No action expected - just musing after noticing this.

const y = l2Pool2d(x, {windowDimensions, layout});
utils.checkShape(y, [1, 2, 2, 1]);
utils.checkValue(y, [
20.639767440550294,
23.302360395462088,
31.654383582688826,
34.51086785347479,
]);
});

it('l2Pool2d dilations default', function() {
const x = new Tensor([1, 1, 4, 4], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
const windowDimensions = [2, 2];
const dilations = [2, 2];
const y = l2Pool2d(x, {windowDimensions, dilations});
utils.checkShape(y, [1, 1, 2, 2]);
utils.checkValue(y, [
14.560219778561036,
16.24807680927192,
21.633307652783937,
23.49468024894146,
]);
});

it('l2Pool2d dilations nhwc', function() {
const x = new Tensor([1, 4, 4, 1], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
const windowDimensions = [2, 2];
const dilations = [2, 2];
const layout = 'nhwc';
const y = l2Pool2d(x, {windowDimensions, dilations, layout});
utils.checkShape(y, [1, 2, 2, 1]);
utils.checkValue(y, [
14.560219778561036,
16.24807680927192,
21.633307652783937,
23.49468024894146,
]);
});

it('l2Pool2d pads default', function() {
const x = new Tensor([1, 1, 5, 5], [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
]);
const windowDimensions = [5, 5];
const padding = [2, 2, 2, 2];
const y = l2Pool2d(x, {windowDimensions, padding});
utils.checkShape(y, [1, 1, 5, 5]);
const expected = [
24.43358344574123, 29.832867780352597,
35.21363372331802, 32.89376840679705,
29.748949561287034, 38.28837943815329,
46.04345773288535, 53.5723809439155,
49.558046773455466, 44.384682042344295,
54.037024344425184, 64.42049363362563,
74.33034373659252, 68.33739825307956,
60.8276253029822, 53.907327887774215,
64.18722614352485, 73.95944834840239,
67.94115100585212, 60.41522986797286,
52.507142371300304, 62.369864518050704,
71.69379331573968, 65.7419196555744,
58.35237784358063,
];
utils.checkValue(y, expected);
});

it('l2Pool2d pads nhwc', function() {
const x = new Tensor([1, 5, 5, 1], [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
]);
const windowDimensions = [5, 5];
const padding = [2, 2, 2, 2];
const layout = 'nhwc';
const y = l2Pool2d(x, {windowDimensions, padding, layout});
utils.checkShape(y, [1, 5, 5, 1]);
const expected = [
24.43358344574123, 29.832867780352597,
35.21363372331802, 32.89376840679705,
29.748949561287034, 38.28837943815329,
46.04345773288535, 53.5723809439155,
49.558046773455466, 44.384682042344295,
54.037024344425184, 64.42049363362563,
74.33034373659252, 68.33739825307956,
60.8276253029822, 53.907327887774215,
64.18722614352485, 73.95944834840239,
67.94115100585212, 60.41522986797286,
52.507142371300304, 62.369864518050704,
71.69379331573968, 65.7419196555744,
58.35237784358063,
];
utils.checkValue(y, expected);
});

it('l2Pool2d autoPad same-upper default', function() {
const x = new Tensor([1, 1, 5, 5], [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
]);
const windowDimensions = [5, 5];
const autoPad = 'same-upper';
const y = l2Pool2d(x, {windowDimensions, autoPad});
utils.checkShape(y, [1, 1, 5, 5]);
const expected = [
24.43358344574123, 29.832867780352597,
35.21363372331802, 32.89376840679705,
29.748949561287034, 38.28837943815329,
46.04345773288535, 53.5723809439155,
49.558046773455466, 44.384682042344295,
54.037024344425184, 64.42049363362563,
74.33034373659252, 68.33739825307956,
60.8276253029822, 53.907327887774215,
64.18722614352485, 73.95944834840239,
67.94115100585212, 60.41522986797286,
52.507142371300304, 62.369864518050704,
71.69379331573968, 65.7419196555744,
58.35237784358063,
];
utils.checkValue(y, expected);
});

it('l2Pool2d autoPad explicit nhwc', function() {
const x = new Tensor([1, 7, 7, 1], [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
]);
const windowDimensions = [4, 4];
const padding = [2, 1, 2, 1];
const strides = [2, 2];
const autoPad = 'explicit';
const layout = 'nhwc';
const y = l2Pool2d(
x, {windowDimensions, autoPad, padding, strides, layout});
utils.checkShape(y, [1, 4, 4, 1]);
const expected = [
12.24744871391589, 19.8997487421324,
24.899799195977465, 24.879710609249457,
40.54626986542659, 60.860496218811754,
67.82329983125268, 63.324560795950255,
76.81145747868608, 112.5344391730816,
120.2331069215131, 109.1146186356347,
90.50414355155237, 131.4610208388783,
138.31124321616085, 124.21352583354198,
];
utils.checkValue(y, expected);
});

it('l2Pool2d autoPad same-lower nhwc', function() {
const x = new Tensor([1, 7, 7, 1], [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
]);
const windowDimensions = [4, 4];
const strides = [2, 2];
const autoPad = 'same-lower';
const layout = 'nhwc';
const y =
l2Pool2d(x, {windowDimensions, autoPad, strides, layout});
utils.checkShape(y, [1, 4, 4, 1]);
const expected = [
12.24744871391589, 19.8997487421324,
24.899799195977465, 24.879710609249457,
40.54626986542659, 60.860496218811754,
67.82329983125268, 63.324560795950255,
76.81145747868608, 112.5344391730816,
120.2331069215131, 109.1146186356347,
90.50414355155237, 131.4610208388783,
138.31124321616085, 124.21352583354198,
];
utils.checkValue(y, expected);
});

it('l2Pool2d autoPad same-upper nhwc', function() {
const x = new Tensor([1, 5, 5, 1], [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
]);
const windowDimensions = [5, 5];
const autoPad = 'same-upper';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, we're still using autoPad for pooling.

@huningxin, you opened webmachinelearning/webnn#326 where we talked about removing autoPad from conv2d, and @Honry removed it from the WebNN EP in microsoft/onnxruntime#18688, but for consistency, wouldn't we want to be explicit with pooling too?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but for consistency, wouldn't we want to be explicit with pooling too?

Yes, we should be consistent and only do explicit padding for pooling.

const layout = 'nhwc';
const y = l2Pool2d(x, {windowDimensions, autoPad, layout});

utils.checkShape(y, [1, 5, 5, 1]);
const expected = [
24.43358344574123, 29.832867780352597,
35.21363372331802, 32.89376840679705,
29.748949561287034, 38.28837943815329,
46.04345773288535, 53.5723809439155,
49.558046773455466, 44.384682042344295,
54.037024344425184, 64.42049363362563,
74.33034373659252, 68.33739825307956,
60.8276253029822, 53.907327887774215,
64.18722614352485, 73.95944834840239,
67.94115100585212, 60.41522986797286,
52.507142371300304, 62.369864518050704,
71.69379331573968, 65.7419196555744,
58.35237784358063,
];
utils.checkValue(y, expected);
});

it('l2Pool2d strides default', function() {
const x = new Tensor([1, 1, 5, 5], [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
]);
const windowDimensions = [2, 2];
const strides = [2, 2];
const y = l2Pool2d(x, {windowDimensions, strides});
utils.checkShape(y, [1, 1, 2, 2]);
const expected = [
9.486832980505138,
13.038404810405298,
28.460498941515414,
32.4037034920393,
];
utils.checkValue(y, expected);
});

it('l2Pool2d strides nhwc', function() {
const x = new Tensor([1, 5, 5, 1], [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
]);
const windowDimensions = [2, 2];
const strides = [2, 2];
const layout = 'nhwc';
const y = l2Pool2d(x, {windowDimensions, strides, layout});
utils.checkShape(y, [1, 2, 2, 1]);
const expected = [
9.486832980505138,
13.038404810405298,
28.460498941515414,
32.4037034920393,
];
utils.checkValue(y, expected);
});
Copy link

@fdwr fdwr Jan 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we add a single element test case here (e.g. const x = new Tensor([1, 1, 1, 1], or wait for the WPT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay,I have added a single element test case.

});