Skip to content

Commit c6ec26d

Browse files
authored
Merge pull request #5736 from lcy-seso/l2_distance
Add the L2 distance layer.
2 parents 569f7c4 + 37190b7 commit c6ec26d

File tree

9 files changed

+295
-17
lines changed

9 files changed

+295
-17
lines changed

doc/api/v2/config/layer.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,11 @@ cos_sim
382382
.. autoclass:: paddle.v2.layer.cos_sim
383383
:noindex:
384384

385+
l2_distance
386+
-----------
387+
.. autoclass:: paddle.v2.layer.l2_distance
388+
:noindex:
389+
385390
trans
386391
-----
387392
.. autoclass:: paddle.v2.layer.trans
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License. */
14+
15+
#include "L2DistanceLayer.h"
16+
#include "paddle/utils/Logging.h"
17+
#include "paddle/utils/Stat.h"
18+
19+
namespace paddle {
20+
21+
REGISTER_LAYER(l2_distance, L2DistanceLayer);
22+
23+
bool L2DistanceLayer::init(const LayerMap& layerMap,
24+
const ParameterMap& parameterMap) {
25+
/* Initialize the basic parent class */
26+
Layer::init(layerMap, parameterMap);
27+
28+
CHECK_EQ(inputLayers_.size(), 2UL) << "The L2DistanceLayer accepts two and "
29+
<< "only two inputs.";
30+
CHECK_EQ(getSize(), 1UL) << "The output dimensionality of L2DistanceLayer "
31+
<< "is fixed to be 1.";
32+
33+
return true;
34+
}
35+
36+
void L2DistanceLayer::forward(PassType passType) {
37+
Layer::forward(passType);
38+
39+
const auto inV1 = getInputValue(0);
40+
const auto inV2 = getInputValue(1);
41+
42+
CHECK(inV1 && inV2);
43+
CHECK_EQ(inV1->getHeight(), inV2->getHeight())
44+
<< "The height of two inputs of this layer must be the same.";
45+
CHECK_EQ(inV1->getWidth(), inV2->getWidth())
46+
<< "The width of two inputs of this layer must be the same.";
47+
48+
int batchSize = inV1->getHeight();
49+
int output_dim = getSize();
50+
{
51+
REGISTER_TIMER_INFO("L2DistanceBpAtvTimer", getName().c_str());
52+
reserveOutput(batchSize, output_dim);
53+
auto outV = getOutputValue();
54+
CHECK(outV) << "The output matrix should not be null.";
55+
56+
Matrix::resizeOrCreate(
57+
inputSub_, inV1->getHeight(), inV1->getWidth(), false, useGpu_);
58+
59+
inputSub_->assign(*inV1);
60+
inputSub_->sub(*inV2);
61+
outV->sumOfProducts(*inputSub_, *inputSub_, 1, 0);
62+
outV->sqrt2(*outV);
63+
}
64+
}
65+
66+
void L2DistanceLayer::backward(const UpdateCallback& callback) {
67+
const auto outG = getOutputGrad();
68+
const auto outV = getOutputValue();
69+
CHECK(outG && outV);
70+
71+
auto inGrad1 = getInputGrad(0);
72+
auto inGrad2 = getInputGrad(1);
73+
74+
{
75+
REGISTER_TIMER_INFO("L2DistanceBpAtvTimer", getName().c_str());
76+
77+
if (inGrad1 || inGrad2) {
78+
outV->scalarDiv(*outV, 1.);
79+
outV->dotMul(*outG, *outV);
80+
}
81+
82+
if (inGrad1) inGrad1->addRowScale(0, *inputSub_, *outV);
83+
84+
if (inGrad2) {
85+
inputSub_->mulScalar(-1.);
86+
inGrad2->addRowScale(0, *inputSub_, *outV);
87+
}
88+
}
89+
}
90+
91+
} // namespace paddle
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License. */
14+
15+
#pragma once
16+
17+
#include "Layer.h"
18+
#include "paddle/math/Matrix.h"
19+
20+
namespace paddle {
21+
22+
/**
23+
* @brief The layer calculates the l2 distance between two input vectors.
24+
* \f[
25+
* f(\bf{x}, \bf{y}) = \sqrt{\sum_{i=1}^D(x_i - y_i)}
26+
* \f]
27+
*
28+
* - Input1: A vector (batchSize * dataDim)
29+
* - Input2: A vector (batchSize * dataDim)
30+
* - Output: A vector (batchSize * 1)
31+
*
32+
* The configuration api is: l2_distance_layer.
33+
*/
34+
35+
class L2DistanceLayer : public Layer {
36+
public:
37+
explicit L2DistanceLayer(const LayerConfig& config) : Layer(config) {}
38+
~L2DistanceLayer() {}
39+
40+
bool init(const LayerMap& layerMap,
41+
const ParameterMap& parameterMap) override;
42+
43+
void forward(PassType passType) override;
44+
void backward(const UpdateCallback& callback = nullptr) override;
45+
46+
private:
47+
// Store the result of subtracting Input2 from Input1 in forward computation,
48+
// which will be reused in backward computation.
49+
MatrixPtr inputSub_;
50+
};
51+
52+
} // namespace paddle

paddle/gserver/tests/test_LayerGrad.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,7 @@ TEST(Layer, maxoutLayer) {
583583
testLayerGrad(config, "maxout", 10, false, useGpu);
584584
}
585585
}
586+
586587
void testFcLayer(string format, size_t nnz) {
587588
TestConfig config;
588589
config.biasSize = 1024;
@@ -2444,6 +2445,25 @@ TEST(Layer, ScaleSubRegionLayer) {
24442445
}
24452446
}
24462447

2448+
TEST(Layer, L2DistanceLayer) {
2449+
TestConfig config;
2450+
config.layerConfig.set_type("l2_distance");
2451+
config.layerConfig.set_size(1);
2452+
config.biasSize = 0;
2453+
2454+
const size_t input_dim = 27;
2455+
const size_t batch_size = 11;
2456+
2457+
config.inputDefs.push_back({INPUT_DATA, "layer_0", input_dim, 0});
2458+
config.inputDefs.push_back({INPUT_DATA, "layer_1", input_dim, 0});
2459+
config.layerConfig.add_inputs();
2460+
config.layerConfig.add_inputs();
2461+
2462+
for (auto useGpu : {false, true}) {
2463+
testLayerGrad(config, "l2_distance", batch_size, false, useGpu);
2464+
}
2465+
}
2466+
24472467
int main(int argc, char** argv) {
24482468
testing::InitGoogleTest(&argc, argv);
24492469
initMain(argc, argv);

python/paddle/trainer/config_parser.py

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3342,17 +3342,45 @@ def __init__(self, name, inputs, **xargs):
33423342
self.set_layer_size(input_layer.size)
33433343

33443344

3345+
@config_layer('cos')
3346+
class CosSimLayer(LayerBase):
3347+
def __init__(self, name, inputs, cos_scale=1, device=None):
3348+
super(CosSimLayer, self).__init__(
3349+
name, 'cos', 1, inputs=inputs, device=device)
3350+
config_assert(
3351+
len(self.inputs) == 2,
3352+
'The CosSimLayer expects two and only two inputs.')
3353+
config_assert(
3354+
self.get_input_layer(0).size == self.get_input_layer(1).size,
3355+
'The two inputs of CosSimLayer must have the same dimensionality.')
3356+
self.config.cos_scale = cos_scale
3357+
3358+
33453359
@config_layer('cos_vm')
33463360
class CosSimVecMatLayer(LayerBase):
33473361
def __init__(self, name, size, inputs, cos_scale=1.0, device=None):
33483362
super(CosSimVecMatLayer, self).__init__(
33493363
name, 'cos_vm', size, inputs=inputs, device=device)
33503364
self.config.cos_scale = cos_scale
33513365
config_assert(
3352-
len(self.inputs) == 2, 'CosSimVecMatLayer must have 2 inputs')
3366+
len(self.inputs) == 2, 'The CosSimVecMatLayer must have 2 inputs.')
33533367
config_assert(
33543368
size * self.get_input_layer(0).size == self.get_input_layer(1).size,
3355-
'Wrong input size for CosSimVecMatLayer')
3369+
'Wrong input size for CosSimVecMatLayer.')
3370+
3371+
3372+
@config_layer('l2_distance')
3373+
class L2DistanceLayer(LayerBase):
3374+
def __init__(self, name, inputs, device=None):
3375+
super(L2DistanceLayer, self).__init__(
3376+
name, 'l2_distance', 1, inputs=inputs, device=device)
3377+
config_assert(
3378+
len(self.inputs) == 2, ('The L2DistanceLayer must have '
3379+
'and only have 2 inputs.'))
3380+
config_assert(
3381+
self.get_input_layer(0).size == self.get_input_layer(1).size,
3382+
('Two inputs of the L2DistanceLayer must have '
3383+
'the same dimensionality.'))
33563384

33573385

33583386
@config_layer('sampling_id')
@@ -3396,18 +3424,6 @@ def __init__(self,
33963424
self.create_bias_parameter(bias, self.config.size)
33973425

33983426

3399-
@config_layer('cos')
3400-
class CosSimLayer(LayerBase):
3401-
def __init__(self, name, inputs, cos_scale=1, device=None):
3402-
super(CosSimLayer, self).__init__(
3403-
name, 'cos', 1, inputs=inputs, device=device)
3404-
config_assert(len(self.inputs) == 2, 'CosSimLayer must have 2 inputs')
3405-
config_assert(
3406-
self.get_input_layer(0).size == self.get_input_layer(1).size,
3407-
'inputs of CosSimLayer must have same dim')
3408-
self.config.cos_scale = cos_scale
3409-
3410-
34113427
@config_layer('tensor')
34123428
class TensorLayer(LayerBase):
34133429
def __init__(self, name, size, inputs, bias=True, **xargs):

python/paddle/trainer_config_helpers/layers.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
'last_seq',
5252
'first_seq',
5353
'cos_sim',
54+
'l2_distance_layer',
5455
'hsigmoid',
5556
'conv_projection',
5657
'square_error_cost',
@@ -168,6 +169,7 @@ class LayerType(object):
168169
COST = 'cost'
169170
COSINE_SIM_VEC = 'cos_vm'
170171
COSINE_SIM = 'cos'
172+
L2_DISTANCE = 'l2_distance'
171173
HSIGMOID = 'hsigmoid'
172174
CONV_LAYER = 'conv'
173175
CONVTRANS_LAYER = 'convt'
@@ -2334,6 +2336,51 @@ def cos_sim(a, b, scale=1, size=1, name=None, layer_attr=None):
23342336
return LayerOutput(name, LayerType.COSINE_SIM, parents=[a, b], size=size)
23352337

23362338

2339+
@wrap_name_default()
2340+
@layer_support()
2341+
def l2_distance_layer(x, y, name=None, layer_attr=None):
2342+
"""
2343+
This layer calculates and returns the Euclidean distance between two input
2344+
vectors x and y. The equation is as follows:
2345+
2346+
.. math::
2347+
l2_distance(\\mathbf{x}, \\mathbf{y}) = \\sqrt{\\sum_{i=1}^D(x_i - y_i)}
2348+
2349+
The output size of this layer is fixed to be 1. Note that the above
2350+
computation is for one sample. Multiple samples are processed in one batch.
2351+
2352+
The example usage is:
2353+
2354+
.. code-block:: python
2355+
2356+
l2_sim = l2_distance(x=layer1, y=layer2)
2357+
2358+
:param name: The name of this layer. It is optional.
2359+
:type name: basestring
2360+
:param x: The first input x for this layer, whose output is a matrix with
2361+
dimensionality N x D. N is the sample number in a mini-batch.
2362+
D is the dimensionality of x's output.
2363+
:type x: LayerOutput
2364+
:param y: The second input y for this layer, whose output is a matrix with
2365+
dimensionality N x D. N is the sample number in a mini-batch.
2366+
D is the dimensionality of y's output.
2367+
:type y: LayerOutput
2368+
:param layer_attr: The extra layer attributes, for example, drop rate.
2369+
See ExtraLayerAttribute for more details.
2370+
:type layer_attr: ExtraLayerAttribute
2371+
:return: The returned LayerOutput object.
2372+
:rtype: LayerOutput
2373+
"""
2374+
2375+
assert isinstance(x, LayerOutput) and isinstance(y, LayerOutput)
2376+
Layer(
2377+
name=name,
2378+
type=LayerType.L2_DISTANCE,
2379+
inputs=[x.name, y.name],
2380+
**ExtraLayerAttribute.to_kwargs(layer_attr))
2381+
return LayerOutput(name, LayerType.L2_DISTANCE, parents=[x, y], size=1)
2382+
2383+
23372384
@wrap_name_default()
23382385
@wrap_bias_attr_default(has_bias=True)
23392386
@wrap_param_attr_default()
@@ -3873,7 +3920,7 @@ def recurrent_layer(input,
38733920
:type input: LayerOutput
38743921
:param act: Activation type. TanhActivation is the default activation.
38753922
:type act: BaseActivation
3876-
:param bias_attr: The parameter attribute for bias. If this parameter is set to
3923+
:param bias_attr: The parameter attribute for bias. If this parameter is set to
38773924
False or an object whose type is not ParameterAttribute,
38783925
no bias is defined. If the parameter is set to True,
38793926
the bias is initialized to zero.

python/paddle/trainer_config_helpers/tests/configs/file_list.sh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ test_prelu_layer test_row_conv test_detection_output_layer test_multibox_loss_la
1010
test_recursive_topology test_gated_unit_layer test_clip_layer test_row_l2_norm_layer
1111
test_kmax_seq_socre_layer test_sub_nested_seq_select_layer test_scale_shift_layer
1212
test_seq_slice_layer test_cross_entropy_over_beam test_roi_pool_layer test_pooling3D_layer
13-
test_conv3d_layer test_deconv3d_layer test_BatchNorm3D test_resize_layer test_scale_sub_region_layer
14-
test_dot_prod_layer)
13+
test_conv3d_layer test_deconv3d_layer test_BatchNorm3D test_resize_layer
14+
test_conv3d_layer test_deconv3d_layer test_BatchNorm3D test_resize_layer
15+
test_scale_sub_region_layer test_dot_prod_layer test_l2_distance_layer)
1516

1617
export whole_configs=(test_split_datasource)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
type: "nn"
2+
layers {
3+
name: "x"
4+
type: "data"
5+
size: 128
6+
active_type: ""
7+
}
8+
layers {
9+
name: "y"
10+
type: "data"
11+
size: 128
12+
active_type: ""
13+
}
14+
layers {
15+
name: "__l2_distance_layer_0__"
16+
type: "l2_distance"
17+
size: 1
18+
active_type: ""
19+
inputs {
20+
input_layer_name: "x"
21+
}
22+
inputs {
23+
input_layer_name: "y"
24+
}
25+
}
26+
input_layer_names: "x"
27+
input_layer_names: "y"
28+
output_layer_names: "__l2_distance_layer_0__"
29+
sub_models {
30+
name: "root"
31+
layer_names: "x"
32+
layer_names: "y"
33+
layer_names: "__l2_distance_layer_0__"
34+
input_layer_names: "x"
35+
input_layer_names: "y"
36+
output_layer_names: "__l2_distance_layer_0__"
37+
is_recurrent_layer_group: false
38+
}
39+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from paddle.trainer_config_helpers import *
2+
3+
outputs(
4+
l2_distance_layer(
5+
x=data_layer(
6+
name='x', size=128), y=data_layer(
7+
name='y', size=128)))

0 commit comments

Comments
 (0)