Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3e19c90
add pdist api
cocoshe Oct 3, 2023
51b908a
move pdist to nn.functional, expose paddle.pdist api
cocoshe Oct 7, 2023
ced239c
clean
cocoshe Oct 7, 2023
9c651d9
clean
cocoshe Oct 7, 2023
13d04ad
fix codestyle
cocoshe Oct 21, 2023
8bcbe47
fix conflict
cocoshe Nov 11, 2023
212bdf7
Merge branch 'develop' of https://github.com/PaddlePaddle/Paddle into…
cocoshe Nov 24, 2023
7bbc22e
remove compute_mode
cocoshe Nov 24, 2023
20f82de
for api name rules
cocoshe Nov 24, 2023
b06dd06
Merge branch 'develop' into pdist_coco_dev
cocoshe Nov 29, 2023
2e259a6
Merge branch 'develop' of https://github.com/PaddlePaddle/Paddle into…
cocoshe Nov 30, 2023
e9dfa5a
Update test_pdist.py
cocoshe Nov 30, 2023
cddec3b
Merge branch 'pdist_coco_dev' of https://github.com/cocoshe/Paddle in…
cocoshe Nov 30, 2023
23d5f7e
add seed
cocoshe Nov 30, 2023
3a9fbfb
fix code sample
cocoshe Dec 1, 2023
e41f9dc
Merge branch 'pdist_coco_dev' of https://github.com/cocoshe/Paddle in…
cocoshe Dec 5, 2023
008ae5b
fix doc
cocoshe Dec 5, 2023
114453a
Update distance.py
cocoshe Dec 5, 2023
171339d
gpu0 to cpu in api doc
cocoshe Dec 9, 2023
8a281b4
gpu0 to cpu in api doc
cocoshe Dec 9, 2023
a9d053c
remove pdist in nn.functional __all__ list
cocoshe Dec 12, 2023
b3816df
Merge branch 'develop' into pdist_coco_dev
cocoshe Dec 12, 2023
3f7b607
Update __init__.py
cocoshe Dec 12, 2023
e3e5abf
fix en doc
cocoshe Dec 13, 2023
8172f28
Update python/paddle/nn/functional/distance.py
cocoshe Dec 13, 2023
0146a37
Update python/paddle/nn/functional/distance.py
cocoshe Dec 13, 2023
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
5 changes: 5 additions & 0 deletions python/paddle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,10 @@
flops,
)

from .nn.functional.distance import ( # noqa: F401
pdist,
)

import paddle.text # noqa: F401
import paddle.vision # noqa: F401

Expand Down Expand Up @@ -702,6 +706,7 @@
'sin_',
'dist',
'cdist',
'pdist',
'unbind',
'meshgrid',
'arange',
Expand Down
3 changes: 2 additions & 1 deletion python/paddle/nn/functional/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
conv3d,
conv3d_transpose,
)
from .distance import pairwise_distance
from .distance import pairwise_distance, pdist
from .extension import diag_embed, gather_tree, sequence_mask, temporal_shift
from .flash_attention import ( # noqa: F401
scaled_dot_product_attention,
Expand Down Expand Up @@ -157,6 +157,7 @@
'conv3d',
'conv3d_transpose',
'pairwise_distance',
'pdist',
Copy link
Contributor

Choose a reason for hiding this comment

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

which path do we recommend users to use? If paddle.pdist is recommended, it cannot be added to this __all__ list. if paddle.nn.functional.pdist, it cannot be added to the __all__ list in python/paddle/__init__.py

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tks for you review, I think I prefer paddle.pdist path, I removed this line then.

'elu',
'elu_',
'gelu',
Expand Down
46 changes: 46 additions & 0 deletions python/paddle/nn/functional/distance.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,49 @@ def pairwise_distance(x, y, p=2.0, epsilon=1e-6, keepdim=False, name=None):
)

return out


def pdist(
x, p=2.0, compute_mode="use_mm_for_euclid_dist_if_necessary", name=None
):
r'''
Computes the p-norm distance between every pair of row vectors in the input.

Args:
x (Tensor): A tensor with shape :math:`N \times M`.
p (float, optional): The value for the p-norm distance to calculate between each vector pair. Default: :math:`2.0`.
compute_mode (str, optional): The mode for compute distance.

- ``use_mm_for_euclid_dist_if_necessary`` , for p = 2.0 and (P > 25 or R > 25), it will use matrix multiplication to calculate euclid distance if possible.
- ``use_mm_for_euclid_dist`` , for p = 2.0, it will use matrix multiplication to calculate euclid distance.
Copy link
Contributor

Choose a reason for hiding this comment

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

仅针对pdist这个API来说,cdist中使用到的这几个mode是否会用到?如果完全用不到我理解这个地方可能不需要添加mode参数。

Copy link
Contributor Author

@cocoshe cocoshe Nov 17, 2023

Choose a reason for hiding this comment

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

仅针对pdist这个API来说,cdist中使用到的这几个mode是否会用到?如果完全用不到我理解这个地方可能不需要添加mode参数。

我这里是把pdist当作一种特殊的cdist来实现:cdist输入x:[P,M], y:[R,M], 输出shape: [P,R],所以我在这里就是通过计算两份相同输入的cdist来实现pdist(令y=x),也就是cdist(x, x), 输入shape:[N, M],输出shape:[N, N],然后取上三角(去除对角线)得到结果。

对照起来看的话,cdist中的P和R对应现在输入[N,M]中的N,那当N较大的时候(大于25)我感觉是不是也可以采取cdist中的矩阵策略,当然不采用矩阵方法的话,应该就是直接norm然后取三角了,不知道这么想对不对

Copy link
Contributor

@zxcd zxcd Nov 22, 2023

Choose a reason for hiding this comment

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

从竞品对照来看,pdist的结果都是比较统一的,不存在N大于25时需要进行额外操作。另外从公式来说cdist和pdist的意义并不完全一致,我建议与竞品保持一致,不需要额外添加该参数。

Copy link
Contributor Author

Choose a reason for hiding this comment

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

从竞品对照来看,pdist的结果都是比较统一的,不存在N大于25时需要进行额外操作。我建议与竞品保持一致,不需要额外添加该参数。

OKK,辛苦review,我之后稍作修改~

- ``donot_use_mm_for_euclid_dist`` , it will not use matrix multiplication to calculate euclid distance.

Default: ``use_mm_for_euclid_dist_if_necessary``.
name (str, optional): For details, please refer to :ref:`api_guide_Name`. Generally, no setting is required. Default: None.

Returns:
Tensor with shape: math:`N(N-1)/2` the dtype is same as input tensor.

Examples:
.. code-block:: python

>>> import paddle
>>> a = paddle.randn([4, 5])
Copy link
Contributor

@zxcd zxcd Nov 30, 2023

Choose a reason for hiding this comment

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

doc中给出seed,不然PR-CI-Static-Check过不了
参考:

>>> paddle.seed(2023)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

doc中给出seed,不然PR-CI-Static-Check过不了 参考:

>>> paddle.seed(2023)

现在添加了,但是好像没作用嘛?

Copy link
Contributor

Choose a reason for hiding this comment

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

添加了seed之后,你的print的结果也会有变化,这块的输出你可以参考报错的内容

Copy link
Contributor Author

Choose a reason for hiding this comment

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

添加了seed之后,你的print的结果也会有变化,这块的输出你可以参考报错的内容

明白了,稍后修改~

>>> a
Tensor(shape=[4, 5], dtype=float32, place=Place(gpu:0), stop_gradient=True,
[[-0.33173719, -0.93648648, -0.01741328, -0.94435263, 2.22178721],
[-0.65466857, 0.10307083, 0.08741203, -0.91078597, 0.72589827],
[ 0.06907391, -0.27584535, 1.35355449, -0.69688839, 0.18408430],
[-0.00939178, -0.32901841, -1.06503606, 0.81856263, 0.16791444]])
>>> pdist_out=paddle.pdist(a)
>>> pdist_out
Tensor(shape=[6], dtype=float32, place=Place(gpu:0), stop_gradient=True,
[1.85331142, 2.58652687, 2.98273396, 1.61549115, 2.28762150, 2.85576940])

'''

x_shape = list(x.shape)
assert len(x_shape) == 2, "The x must be 2-dimensional"
d = paddle.cdist(x, x, p, compute_mode)
mask = ~paddle.tril(paddle.ones(d.shape, dtype='bool'))
return paddle.masked_select(d, mask)
143 changes: 143 additions & 0 deletions test/legacy_test/test_pdist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import unittest

import numpy as np

import paddle


def ref_pdist(x, p=2.0):
dist = np.linalg.norm(x[..., None, :] - x[None, :, :], ord=p, axis=-1)
res = []
rows, cols = dist.shape
for i in range(rows):
for j in range(cols):
if i >= j:
continue
res.append(dist[i][j])
return np.array(res)


class TestpdistAPI(unittest.TestCase):
def setUp(self):
self.x = np.random.rand(10, 20).astype('float32')
self.p = 2.0
self.compute_mode = "use_mm_for_euclid_dist_if_necessary"
self.init_input()
self.place = (
paddle.CUDAPlace(0)
if paddle.is_compiled_with_cuda()
else paddle.CPUPlace()
)

def init_input(self):
pass

def test_static_api(self):
paddle.enable_static()
with paddle.static.program_guard(paddle.static.Program()):
x = paddle.static.data('x', self.x.shape, dtype=self.x.dtype)
out = paddle.pdist(x, self.p, self.compute_mode)
exe = paddle.static.Executor(self.place)
res = exe.run(feed={'x': self.x}, fetch_list=[out])
out_ref = ref_pdist(self.x, self.p)
np.testing.assert_allclose(out_ref, res[0], rtol=1e-5, atol=1e-5)
Copy link
Contributor

Choose a reason for hiding this comment

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

这里应该不需要使用rtol, atol?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

>>> x = np.random.rand(3, 4).astype('float64')
>>> t_x = paddle.to_tensor(x)
>>> x == t_x.numpy()
array([[ True,  True,  True,  True],
       [ True,  True,  True,  True],
       [ True,  True,  True,  True]])
>>> np_norm = np.linalg.norm(x, axis=-1)
>>> pd_norm = paddle.linalg.norm(t_x, axis=-1)
>>> np_norm == pd_norm.numpy()
array([ True,  True,  True])


>>> x = np.random.rand(5, 6).astype('float64')
>>> t_x = paddle.to_tensor(x)
>>> np_norm = np.linalg.norm(x, axis=-1)
>>> pd_norm = paddle.linalg.norm(t_x, axis=-1)
>>> np_norm == pd_norm.numpy()
array([ True,  True,  True,  True,  True])


>>> x = np.random.rand(5, 6).astype('float64')
>>> t_x = paddle.to_tensor(x)
>>> np_norm = np.linalg.norm(x, axis=-1)
>>> pd_norm = paddle.linalg.norm(t_x, axis=-1)
>>> np_norm == pd_norm.numpy()
array([ True,  True,  True,  True, False])
>>> 
>>> x = np.random.rand(5, 6).astype('float64')
>>> t_x = paddle.to_tensor(x)
>>> np_norm = np.linalg.norm(x, axis=-1)
>>> pd_norm = paddle.linalg.norm(t_x, axis=-1)
>>> np_norm == pd_norm.numpy()
array([ True,  True,  True,  True, False])
>>> 
>>> x = np.random.rand(5, 6).astype('float64')
>>> t_x = paddle.to_tensor(x)
>>> np_norm = np.linalg.norm(x, axis=-1)
>>> pd_norm = paddle.linalg.norm(t_x, axis=-1)
>>> np_norm == pd_norm.numpy()
array([ True,  True,  True,  True, False])
>>> 
>>> x = np.random.rand(5, 6).astype('float64')
>>> t_x = paddle.to_tensor(x)
>>> np_norm = np.linalg.norm(x, axis=-1)
>>> pd_norm = paddle.linalg.norm(t_x, axis=-1)
>>> np_norm == pd_norm.numpy()
array([False, False, False,  True,  True])
>>> 
>>> x = np.random.rand(5, 6).astype('float64')
>>> t_x = paddle.to_tensor(x)
>>> np_norm = np.linalg.norm(x, axis=-1)
>>> pd_norm = paddle.linalg.norm(t_x, axis=-1)
>>> np_norm == pd_norm.numpy()
array([ True,  True, False, False,  True])
>>> 

如果不用rtol atol的话精度过不了。我进行了一些尝试,发现norm看样子像是没和numpy对齐,在cdist的单测中也是放宽了精度

np.testing.assert_allclose(out_ref, res[0], rtol=1e-5, atol=1e-5)


def test_dygraph_api(self):
paddle.disable_static(self.place)
x = paddle.to_tensor(self.x)
out = paddle.pdist(x, self.p, self.compute_mode)
out_ref = ref_pdist(self.x, self.p)
np.testing.assert_allclose(out_ref, out.numpy(), rtol=1e-5, atol=1e-5)
paddle.enable_static()


class TestpdistAPICase1(TestpdistAPI):
Copy link
Contributor

Choose a reason for hiding this comment

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

规范一下测试命名

def init_input(self):
self.p = 0


class TestpdistAPICase2(TestpdistAPI):
def init_input(self):
self.p = 1.0


class TestpdistAPICase3(TestpdistAPI):
def init_input(self):
self.p = 3.0


class TestpdistAPICase4(TestpdistAPI):
def init_input(self):
self.p = 1.5


class TestpdistAPICase5(TestpdistAPI):
def init_input(self):
self.p = 2.5


class TestpdistAPICase6(TestpdistAPI):
def init_input(self):
self.p = float('inf')


class TestpdistAPICase7(TestpdistAPI):
def init_input(self):
self.x = np.random.rand(50, 20).astype('float64')
self.compute_mode = "use_mm_for_euclid_dist"


class TestpdistAPICase8(TestpdistAPI):
def init_input(self):
self.x = np.random.rand(50, 20).astype('float64')
self.compute_mode = "donot_use_mm_for_euclid_dist"


class TestpdistAPICase9(TestpdistAPI):
def init_input(self):
self.x = np.random.rand(500, 100).astype('float64')

def test_static_api(self):
paddle.enable_static()
with paddle.static.program_guard(paddle.static.Program()):
x = paddle.static.data('x', self.x.shape, dtype=self.x.dtype)
out0 = paddle.pdist(x, self.p, self.compute_mode)
out1 = paddle.pdist(x, self.p, "donot_use_mm_for_euclid_dist")
out2 = paddle.pdist(x, self.p, "use_mm_for_euclid_dist")
exe = paddle.static.Executor(self.place)
res = exe.run(feed={'x': self.x}, fetch_list=[out0, out1, out2])
out_ref = ref_pdist(self.x, self.p)
np.testing.assert_allclose(out_ref, res[0])
np.testing.assert_allclose(out_ref, res[1])
np.testing.assert_allclose(out_ref, res[2])

def test_dygraph_api(self):
paddle.disable_static(self.place)
x = paddle.to_tensor(self.x)
out0 = paddle.pdist(x, self.p, self.compute_mode)
out1 = paddle.pdist(x, self.p, "donot_use_mm_for_euclid_dist")
out2 = paddle.pdist(x, self.p, "use_mm_for_euclid_dist")
out_ref = ref_pdist(self.x, self.p)
np.testing.assert_allclose(out_ref, out0.numpy())
np.testing.assert_allclose(out_ref, out1.numpy())
np.testing.assert_allclose(out_ref, out2.numpy())
paddle.enable_static()


if __name__ == '__main__':
paddle.enable_static()
unittest.main()