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

【PaddlePaddle Hackathon 3 No.12】为 Paddle 新增 pairwise_distance #44161

Merged
merged 10 commits into from Jul 29, 2022
335 changes: 293 additions & 42 deletions python/paddle/fluid/tests/unittests/test_pairwise_distance.py
Expand Up @@ -20,24 +20,65 @@
import unittest


def pairwise_distance(x, y, p=2.0, epsilon=1e-6, keepdim=False):
return np.linalg.norm(x - y, ord=p, axis=1, keepdims=keepdim)
def np_pairwise_distance(x, y, p=2.0, epsilon=1e-6, keepdim=False):

distance = np.linalg.norm(x - y + epsilon, ord=p, axis=-1, keepdims=keepdim)
# Paddle currently has not supported for 0-d Tensors, so even if keep_dim is False,
# and neither x nor y is batched, a Tensor of shape (1, ) is returned
if distance.ndim == 0:
distance = np.expand_dims(distance, axis=0)
return distance

def test_static(x_np, y_np, p=2.0, epsilon=1e-6, keepdim=False):

def call_pairwise_distance_layer(x, y, p=2., epsilon=1e-6, keepdim='False'):
pairwise_distance = paddle.nn.PairwiseDistance(p=p,
epsilon=epsilon,
keepdim=keepdim)
distance = pairwise_distance(x=x, y=y)
return distance


def call_pairwise_distance_functional(x,
y,
p=2.,
epsilon=1e-6,
keepdim='False'):
distance = paddle.nn.functional.pairwise_distance(x=x,
y=y,
p=p,
epsilon=epsilon,
keepdim=keepdim)
return distance


def test_static(place,
x_np,
y_np,
p=2.0,
epsilon=1e-6,
keepdim=False,
functional=False):
prog = paddle.static.Program()
startup_prog = paddle.static.Program()

place = fluid.CUDAPlace(
0) if paddle.fluid.core.is_compiled_with_cuda() else fluid.CPUPlace()

paddle.enable_static()
with paddle.static.program_guard(prog, startup_prog):
x = paddle.fluid.data(name='x', shape=x_np.shape, dtype=x_np.dtype)
y = paddle.fluid.data(name='y', shape=y_np.shape, dtype=x_np.dtype)
dist = paddle.nn.layer.distance.PairwiseDistance(p=p,

if functional:
distance = call_pairwise_distance_functional(x=x,
y=y,
p=p,
epsilon=epsilon,
keepdim=keepdim)
distance = dist(x, y)
else:
distance = call_pairwise_distance_layer(x=x,
y=y,
p=p,
epsilon=epsilon,
keepdim=keepdim)
exe = paddle.static.Executor(place)
static_ret = exe.run(prog,
feed={
Expand All @@ -46,69 +87,279 @@ def test_static(x_np, y_np, p=2.0, epsilon=1e-6, keepdim=False):
},
fetch_list=[distance])
static_ret = static_ret[0]
paddle.disable_static()
return static_ret


def test_dygraph(x_np, y_np, p=2.0, epsilon=1e-6, keepdim=False):
paddle.disable_static()
def test_dygraph(place,
x_np,
y_np,
p=2.0,
epsilon=1e-6,
keepdim=False,
functional=False):
x = paddle.to_tensor(x_np)
y = paddle.to_tensor(y_np)
dist = paddle.nn.layer.distance.PairwiseDistance(p=p,
epsilon=epsilon,
keepdim=keepdim)
distance = dist(x, y)
dygraph_ret = distance.numpy()
paddle.enable_static()
if functional:
dy_distance = call_pairwise_distance_functional(x=x,
y=y,
p=p,
epsilon=epsilon,
keepdim=keepdim)
else:
dy_distance = call_pairwise_distance_layer(x=x,
y=y,
p=p,
epsilon=epsilon,
keepdim=keepdim)
dygraph_ret = dy_distance.numpy()
return dygraph_ret


def test_legacy_dygraph(place,
x_np,
y_np,
p=2.0,
epsilon=1e-6,
keepdim=False,
functional=False):
paddle.fluid.framework._enable_legacy_dygraph()
Copy link
Member

@SigureMo SigureMo Jul 7, 2022

Choose a reason for hiding this comment

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

image

截图来自 PR-CI-APPROVAL

这里利用 _enable_legacy_dygraph 开启 legacy 动态图的原因是原来的 PairwiseDistance(class API)是包含 legacy 动态图代码的,如果不添加相关测试的话,Coverage CI 过不了,因此利用该 API 添加了相关测试,如果该逻辑不再推荐添加的话,我们会删去相关逻辑,并删去相关测试代码

Choose a reason for hiding this comment

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

这个到时可以让对应的同学 approve 就好。目前还不能删掉这个逻辑。

Copy link
Member

Choose a reason for hiding this comment

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

好哒~明白~

x = paddle.to_tensor(x_np)
y = paddle.to_tensor(y_np)
if functional:
legacy_distance = call_pairwise_distance_functional(x=x,
y=y,
p=p,
epsilon=epsilon,
keepdim=keepdim)
else:
legacy_distance = call_pairwise_distance_layer(x=x,
y=y,
p=p,
epsilon=epsilon,
keepdim=keepdim)
legacy_ret = legacy_distance.numpy()
paddle.fluid.framework._disable_legacy_dygraph()
return legacy_ret


class TestPairwiseDistance(unittest.TestCase):

def test_pairwise_distance(self):
all_shape = [[100, 100], [4, 5, 6, 7]]
epsilon = 1e-6
all_shape = [[5], [100, 100], [4, 5, 6, 7]]
Copy link
Member

Choose a reason for hiding this comment

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

因为我们希望修改 paddle 的 PairwiseDistance 的行为,让它支持两个 1d 输入,我支持把 axis 改成 -1. 在文档所述的 defined behavior 范围内,这个修改不会破坏兼容性。

好的明白。

但是测试中又使用了高维的输入,这就是在测试 undefined behavior, 如果能 define 那就不是 undefined behavior 了,所以我觉得需要修改这个测试文件。

这里有一个同样的问题,原本的单测(fluid 下面的)也是对 undefined behavior([4, 5, 6, 7])进行了测试,这里是不是也应该删掉呀~

Choose a reason for hiding this comment

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

好的呀。

dtypes = ['float32', 'float64']
p_list = [-1, 0, 1, 2, np.inf, -np.inf]
places = [paddle.CPUPlace()]
if paddle.device.is_compiled_with_cuda():
places.append(paddle.CUDAPlace(0))
keeps = [False, True]
for shape in all_shape:
for dtype in dtypes:
for keepdim in keeps:
x_np = np.random.random(shape).astype(dtype)
y_np = np.random.random(shape).astype(dtype)

static_ret = test_static(x_np, y_np, keepdim=keepdim)
dygraph_ret = test_dygraph(x_np, y_np, keepdim=keepdim)
excepted_value = pairwise_distance(x_np,
for place in places:
for shape in all_shape:
for dtype in dtypes:
for p in p_list:
for keepdim in keeps:
x_np = np.random.random(shape).astype(dtype)
y_np = np.random.random(shape).astype(dtype)

static_ret = test_static(place,
x_np,
y_np,
p,
epsilon=epsilon,
keepdim=keepdim)
dygraph_ret = test_dygraph(place,
x_np,
y_np,
p,
epsilon=epsilon,
keepdim=keepdim)
legacy_ret = test_legacy_dygraph(place,
x_np,
y_np,
p,
epsilon=epsilon,
keepdim=keepdim)
excepted_value = np_pairwise_distance(
x_np, y_np, p, epsilon=epsilon, keepdim=keepdim)

self.assertEqual(static_ret.shape,
excepted_value.shape)
self.assertEqual(dygraph_ret.shape,
excepted_value.shape)
self.assertEqual(legacy_ret.shape,
excepted_value.shape)

self.assertTrue(
np.allclose(static_ret, excepted_value))
self.assertTrue(
np.allclose(dygraph_ret, excepted_value))
self.assertTrue(
np.allclose(legacy_ret, excepted_value))

static_functional_ret = test_static(place,
x_np,
y_np,
p,
epsilon=epsilon,
keepdim=keepdim)
dygraph_functional_ret = test_dygraph(
place,
x_np,
y_np,
p,
epsilon=epsilon,
keepdim=keepdim)
legacy_functional_ret = test_legacy_dygraph(
place,
x_np,
y_np,
p,
epsilon=epsilon,
keepdim=keepdim)

self.assertTrue(np.allclose(static_ret, dygraph_ret))
self.assertTrue(np.allclose(static_ret, excepted_value))
self.assertTrue(np.allclose(dygraph_ret, excepted_value))
self.assertEqual(static_functional_ret.shape,
excepted_value.shape)
self.assertEqual(dygraph_functional_ret.shape,
excepted_value.shape)
self.assertEqual(legacy_functional_ret.shape,
excepted_value.shape)

def test_pairwise_distance_broadcast(self):
self.assertTrue(
np.allclose(static_functional_ret,
excepted_value))
self.assertTrue(
np.allclose(dygraph_functional_ret,
excepted_value))
self.assertTrue(
np.allclose(legacy_functional_ret,
excepted_value))

def test_pairwise_distance_broadcast_1(self):
shape_x = [100, 100]
shape_y = [100, 1]
epsilon = 1e-6
keepdim = False
place = paddle.CPUPlace()
x_np = np.random.random(shape_x).astype('float32')
y_np = np.random.random(shape_y).astype('float32')
static_ret = test_static(x_np, y_np, keepdim=keepdim)
dygraph_ret = test_dygraph(x_np, y_np, keepdim=keepdim)
excepted_value = pairwise_distance(x_np, y_np, keepdim=keepdim)
self.assertTrue(np.allclose(static_ret, dygraph_ret))
static_ret = test_static(place=place,
x_np=x_np,
y_np=y_np,
epsilon=epsilon,
keepdim=keepdim)
dygraph_ret = test_dygraph(place=place,
x_np=x_np,
y_np=y_np,
epsilon=epsilon,
keepdim=keepdim)
legacy_ret = test_legacy_dygraph(place=place,
x_np=x_np,
y_np=y_np,
epsilon=epsilon,
keepdim=keepdim)
excepted_value = np_pairwise_distance(x_np,
y_np,
epsilon=epsilon,
keepdim=keepdim)

self.assertEqual(static_ret.shape, excepted_value.shape)
self.assertEqual(dygraph_ret.shape, excepted_value.shape)
self.assertEqual(legacy_ret.shape, excepted_value.shape)

self.assertTrue(np.allclose(static_ret, excepted_value))
self.assertTrue(np.allclose(dygraph_ret, excepted_value))
self.assertTrue(np.allclose(legacy_ret, excepted_value))

static_functional_ret = test_static(place=place,
x_np=x_np,
y_np=y_np,
epsilon=epsilon,
keepdim=keepdim,
functional=True)
dygraph_functional_ret = test_dygraph(place=place,
x_np=x_np,
y_np=y_np,
epsilon=epsilon,
keepdim=keepdim,
functional=True)
legacy_functional_ret = test_legacy_dygraph(place=place,
x_np=x_np,
y_np=y_np,
epsilon=epsilon,
keepdim=keepdim,
functional=True)

self.assertEqual(static_functional_ret.shape, excepted_value.shape)
self.assertEqual(dygraph_functional_ret.shape, excepted_value.shape)
self.assertEqual(legacy_functional_ret.shape, excepted_value.shape)

def test_pairwise_distance_different_p(self):
shape = [100, 100]
self.assertTrue(np.allclose(static_functional_ret, excepted_value))
self.assertTrue(np.allclose(dygraph_functional_ret, excepted_value))
self.assertTrue(np.allclose(legacy_functional_ret, excepted_value))

def test_pairwise_distance_broadcast_2(self):
shape_x = [100, 100]
shape_y = [100]
epsilon = 1e-6
keepdim = False
p = 3.0
x_np = np.random.random(shape).astype('float32')
y_np = np.random.random(shape).astype('float32')
static_ret = test_static(x_np, y_np, p=p, keepdim=keepdim)
dygraph_ret = test_dygraph(x_np, y_np, p=p, keepdim=keepdim)
excepted_value = pairwise_distance(x_np, y_np, p=p, keepdim=keepdim)
self.assertTrue(np.allclose(static_ret, dygraph_ret))
place = paddle.CPUPlace()
x_np = np.random.random(shape_x).astype('float32')
y_np = np.random.random(shape_y).astype('float32')
static_ret = test_static(place=place,
x_np=x_np,
y_np=y_np,
epsilon=epsilon,
keepdim=keepdim)
dygraph_ret = test_dygraph(place=place,
x_np=x_np,
y_np=y_np,
epsilon=epsilon,
keepdim=keepdim)
legacy_ret = test_legacy_dygraph(place=place,
x_np=x_np,
y_np=y_np,
epsilon=epsilon,
keepdim=keepdim)
excepted_value = np_pairwise_distance(x_np,
y_np,
epsilon=epsilon,
keepdim=keepdim)

self.assertEqual(static_ret.shape, excepted_value.shape)
self.assertEqual(dygraph_ret.shape, excepted_value.shape)
self.assertEqual(legacy_ret.shape, excepted_value.shape)

self.assertTrue(np.allclose(static_ret, excepted_value))
self.assertTrue(np.allclose(dygraph_ret, excepted_value))
self.assertTrue(np.allclose(legacy_ret, excepted_value))

static_functional_ret = test_static(place=place,
x_np=x_np,
y_np=y_np,
epsilon=epsilon,
keepdim=keepdim,
functional=True)
dygraph_functional_ret = test_dygraph(place=place,
x_np=x_np,
y_np=y_np,
epsilon=epsilon,
keepdim=keepdim,
functional=True)
legacy_functional_ret = test_legacy_dygraph(place=place,
x_np=x_np,
y_np=y_np,
epsilon=epsilon,
keepdim=keepdim,
functional=True)

self.assertEqual(static_functional_ret.shape, excepted_value.shape)
self.assertEqual(dygraph_functional_ret.shape, excepted_value.shape)
self.assertEqual(legacy_functional_ret.shape, excepted_value.shape)

self.assertTrue(np.allclose(static_functional_ret, excepted_value))
self.assertTrue(np.allclose(dygraph_functional_ret, excepted_value))
self.assertTrue(np.allclose(legacy_functional_ret, excepted_value))


if __name__ == "__main__":
Expand Down