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 第四期】No.6:为 Paddle 新增 ldexp API #51395

Merged
merged 26 commits into from
Jun 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f7e08fb
add ldexp api
longranger2 Mar 7, 2023
fc3e18e
Merge branch 'PaddlePaddle:develop' into ldexp
longranger2 Mar 8, 2023
f5021b8
fix ldexp
longranger2 Mar 8, 2023
79c798a
Update math.py
longranger2 Mar 9, 2023
677b152
Merge branch 'PaddlePaddle:develop' into ldexp
longranger2 Mar 9, 2023
1d2c77d
Merge branch 'develop' into ldexp
longranger2 Mar 19, 2023
1e0a6d7
Merge branch 'ldexp' of https://github.com/longranger2/Paddle into ldexp
longranger2 Apr 6, 2023
d182aa0
fix bug
longranger2 Apr 6, 2023
44aa6dd
rewrite the ldexp
longranger2 May 15, 2023
dc62e14
Simplify ldexp implementation
longranger2 May 16, 2023
344ff06
Simplify ldexp implementation
longranger2 May 16, 2023
7d30779
Merge branch 'develop' into ldexp
longranger2 May 17, 2023
6ca9231
fix codestyle
longranger2 May 17, 2023
c9d0f91
Update math.py
longranger2 May 19, 2023
cfdc729
Update math.py
longranger2 May 19, 2023
f056dc1
Merge branch 'PaddlePaddle:develop' into ldexp
longranger2 May 25, 2023
8926006
modify the test_ldexp.py
longranger2 May 31, 2023
ccc6341
fix the input bug of np.ldexp
longranger2 Jun 1, 2023
b83daa9
fix the bug of np.ldexp in windows
longranger2 Jun 1, 2023
853aae9
modify the ldexp function and add the dtype check of output
longranger2 Jun 2, 2023
91d61f0
Update test_ldexp.py
longranger2 Jun 2, 2023
b841b89
fix the dtype
longranger2 Jun 5, 2023
e44655e
Merge branch 'ldexp' of https://github.com/longranger2/Paddle into ldexp
longranger2 Jun 5, 2023
a642882
Merge branch 'develop' into ldexp
longranger2 Jun 5, 2023
ca3fae5
fix codestyle
longranger2 Jun 6, 2023
3b576b1
Update python/paddle/tensor/math.py
longranger2 Jun 6, 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
2 changes: 2 additions & 0 deletions python/paddle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@
from .tensor.math import sgn # noqa: F401
from .tensor.math import take # noqa: F401
from .tensor.math import frexp # noqa: F401
from .tensor.math import ldexp # noqa: F401
from .tensor.math import trapezoid # noqa: F401
from .tensor.math import cumulative_trapezoid # noqa: F401
from .tensor.math import vander # noqa: F401
Expand Down Expand Up @@ -699,6 +700,7 @@
'triu_indices',
'take',
'frexp',
'ldexp',
'trapezoid',
'cumulative_trapezoid',
'polar',
Expand Down
2 changes: 2 additions & 0 deletions python/paddle/tensor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@
from .math import sgn # noqa: F401
from .math import take # noqa: F401
from .math import frexp # noqa: F401
from .math import ldexp # noqa: F401
from .math import trapezoid # noqa: F401
from .math import cumulative_trapezoid # noqa: F401
from .math import sigmoid # noqa: F401
Expand Down Expand Up @@ -549,6 +550,7 @@
'bucketize',
'sgn',
'frexp',
'ldexp',
'trapezoid',
'cumulative_trapezoid',
'polar',
Expand Down
52 changes: 52 additions & 0 deletions python/paddle/tensor/math.py
Original file line number Diff line number Diff line change
Expand Up @@ -5785,3 +5785,55 @@ def polygamma(x, n, name=None):
attrs={'n': n},
)
return out


def ldexp(x, y, name=None):
"""
Compute the result of multiplying x by 2 to the power of y. The equation is:

.. math::
out = x * 2^{y}

Args:
x (Tensor): The input Tensor, the data type is float32, float64, int32 or int64.
y (Tensor): A Tensor of exponents, typically integers.
name (str, optional): Name for the operation (optional, default is None).For more information, please refer to :ref:`api_guide_Name`.

Returns:
out (Tensor): An N-D Tensor. If x, y have different shapes and are "broadcastable", the resulting tensor shape is the shape of x and y after broadcasting. If x, y have the same shape, its shape is the same as x and y. And the data type is float32 or float64.

Examples:

.. code-block:: python

import paddle

#example1
x = paddle.to_tensor([1, 2, 3], dtype='float32')
y = paddle.to_tensor([2, 3, 4], dtype='int32')
res = paddle.ldexp(x, y)
print(res)
Copy link
Contributor

Choose a reason for hiding this comment

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

示例可以多举一个。比如 y = paddle.to_tensor([2],这样有broadcast的例子了

Copy link
Contributor Author

Choose a reason for hiding this comment

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

已经添加好了~

# Tensor(shape=[3], dtype=float32, place=CUDAPlace(0), stop_gradient=True,
# [4., 16., 48.])

#example2
x = paddle.to_tensor([1, 2, 3], dtype='float32')
y = paddle.to_tensor([2], dtype='int32')
res = paddle.ldexp(x, y)
print(res)
# Tensor(shape=[3], dtype=float32, place=CUDAPlace(0), stop_gradient=True,
# [4., 8., 12.])

"""
if not isinstance(x, (paddle.Tensor, Variable)):
raise TypeError(f"x must be tensor type, but got {type(x)}")
if not isinstance(y, (paddle.Tensor, Variable)):
raise TypeError(f"y must be tensor type, but got {type(y)}")
if x.dtype == paddle.float64 or y.dtype == paddle.float64:
out_dtype = paddle.float64
else:
out_dtype = paddle.get_default_dtype()
x = paddle.cast(x, dtype=out_dtype)
y = paddle.cast(y, dtype=out_dtype)
two = paddle.to_tensor(2, dtype=out_dtype)
return paddle.multiply(x, paddle.pow(two, y))
Copy link
Contributor

Choose a reason for hiding this comment

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

这样能保证,输入x的dtype是int,输出也是int类型么?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

不能,当时这么设置的原因在下面提到了

Copy link
Contributor Author

@longranger2 longranger2 Jun 1, 2023

Choose a reason for hiding this comment

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

而且还有一个问题就是,虽然x的dtype是int,y输入的dtype也是int,但y是负的,这时候输出类型也应该为float,所以我觉得输出类型设置为float好些

Copy link
Contributor

Choose a reason for hiding this comment

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

所有目前我的想法是计算的时候转为float32或者float64,最后输出的时候再根据x的类型,在进一步转换,不知道这个思路是否可以呢?

这个不行,这不算支持int类型哈。

Copy link
Contributor

Choose a reason for hiding this comment

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

这里的two, y为什么要cast成float64类型,用int类型也可以吧?

Copy link
Contributor Author

@longranger2 longranger2 Jun 1, 2023

Choose a reason for hiding this comment

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

主要当时测试paddle.pow的时候,输入的是int类型时候,如果是负数的情况会发生精度损失,直接为0
下图为设置为int64的情况:

image

下图设置为float64的情况

image

Copy link
Contributor

Choose a reason for hiding this comment

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

>>> torch.pow(torch.tensor([2]),-2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: Integers to negative integer powers are not allowed.
>>> torch.pow(torch.tensor([2.0]),-2)
tensor([0.2500])

torch如果指数是负数,是不支持x是int的。可以提一个issue,说明下此处不一致。

Copy link
Contributor Author

@longranger2 longranger2 Jun 1, 2023

Choose a reason for hiding this comment

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

torch如果指数是负数的时候会报错,但如果指数为负数且是tensor的时候的时候是可以正常计算的,不过也是会发生相应的精度问题
image

172 changes: 172 additions & 0 deletions test/legacy_test/test_ldexp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Copyright (c) 2020 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
from paddle.fluid import core
from paddle.static import Program, program_guard

DYNAMIC = 1
STATIC = 2


def _run_ldexp(mode, x, y, device='cpu'):
# dynamic mode
if mode == DYNAMIC:
paddle.disable_static()
# Set device
paddle.set_device(device)
x_ = paddle.to_tensor(x)
# y is scalar
if isinstance(y, (int)):
y_ = y
# y is tensor
else:
y_ = paddle.to_tensor(y)
res = paddle.ldexp(x_, y_)
return res.numpy()
# static graph mode
elif mode == STATIC:
paddle.enable_static()
# y is scalar
if isinstance(y, (int)):
with program_guard(Program(), Program()):
x_ = paddle.static.data(name="x", shape=x.shape, dtype=x.dtype)
y_ = y
res = paddle.ldexp(x_, y_)
place = (
paddle.CPUPlace()
if device == 'cpu'
else paddle.CUDAPlace(0)
)
exe = paddle.static.Executor(place)
outs = exe.run(feed={'x': x, 'y': y}, fetch_list=[res])
return outs[0]
# y is tensor
else:
with program_guard(Program(), Program()):
x_ = paddle.static.data(name="x", shape=x.shape, dtype=x.dtype)
y_ = paddle.static.data(name="y", shape=y.shape, dtype=y.dtype)
res = paddle.ldexp(x_, y_)
place = (
paddle.CPUPlace()
if device == 'cpu'
else paddle.CUDAPlace(0)
)
exe = paddle.static.Executor(place)
outs = exe.run(feed={'x': x, 'y': y}, fetch_list=[res])
return outs[0]


def check_dtype(input, desired_dtype):
if input.dtype != desired_dtype:
raise ValueError(
"The expected data type to be obtained is {}, but got {}".format(
desired_dtype, input.dtype
)
)


class TestLdexpAPI(unittest.TestCase):
def setUp(self):
self.places = ['cpu']
if core.is_compiled_with_cuda():
self.places.append('gpu')

def test_ldexp(self):
np.random.seed(7)
for place in self.places:
# test 1-d float tensor and 1-d int tensor
dims = (np.random.randint(200, 300),)
x = (np.random.rand(*dims) * 10).astype(np.float64)
y = (np.random.randint(-10, 10, dims)).astype(np.int32)
res = _run_ldexp(DYNAMIC, x, y, place)
check_dtype(res, np.float64)
np.testing.assert_allclose(res, np.ldexp(x, y))
res = _run_ldexp(STATIC, x, y, place)
check_dtype(res, np.float64)
np.testing.assert_allclose(res, np.ldexp(x, y))

dims = (np.random.randint(200, 300),)
x = (np.random.rand(*dims) * 10).astype(np.float32)
y = (np.random.randint(-10, 10, dims)).astype(np.int32)
res = _run_ldexp(DYNAMIC, x, y, place)
check_dtype(res, np.float32)
np.testing.assert_allclose(res, np.ldexp(x, y))
res = _run_ldexp(STATIC, x, y, place)
check_dtype(res, np.float32)
np.testing.assert_allclose(res, np.ldexp(x, y))

# test 1-d int tensor and 1-d int tensor
dims = (np.random.randint(200, 300),)
x = (np.random.randint(-10, 10, dims)).astype(np.int64)
y = (np.random.randint(-10, 10, dims)).astype(np.int32)
res = _run_ldexp(DYNAMIC, x, y, place)
check_dtype(res, np.float32)
np.testing.assert_allclose(res, np.ldexp(x, y))
res = _run_ldexp(STATIC, x, y, place)
check_dtype(res, np.float32)
np.testing.assert_allclose(res, np.ldexp(x, y))

dims = (np.random.randint(200, 300),)
x = (np.random.randint(-10, 10, dims)).astype(np.int32)
y = (np.random.randint(-10, 10, dims)).astype(np.int32)
res = _run_ldexp(DYNAMIC, x, y, place)
check_dtype(res, np.float32)
np.testing.assert_allclose(res, np.ldexp(x, y))
res = _run_ldexp(STATIC, x, y, place)
check_dtype(res, np.float32)
np.testing.assert_allclose(res, np.ldexp(x, y))

# test broadcast
dims = (
np.random.randint(1, 10),
np.random.randint(5, 10),
np.random.randint(5, 10),
)
x = (np.random.rand(*dims) * 10).astype(np.float64)
y = (np.random.randint(-10, 10, dims[-1])).astype(np.int32)
res = _run_ldexp(DYNAMIC, x, y)
check_dtype(res, np.float64)
np.testing.assert_allclose(res, np.ldexp(x, y))
res = _run_ldexp(STATIC, x, y)
check_dtype(res, np.float64)
np.testing.assert_allclose(res, np.ldexp(x, y))


class TestLdexpError(unittest.TestCase):
"""TestLdexpError."""

def test_errors(self):
"""test_errors."""
np.random.seed(7)

# test 1-d float and int tensor
dims = (np.random.randint(200, 300),)
x = (np.random.rand(*dims) * 10).astype(np.float64)
y = (np.random.randint(-10, 10, dims)).astype(np.int32)
self.assertRaises(TypeError, paddle.ldexp, x, paddle.to_tensor(y))

# test 1-d float tensor and int
dims = (np.random.randint(200, 300),)
x = (np.random.rand(*dims) * 10).astype(np.float64)
y = (np.random.randint(-10, 10, dims)).astype(np.int32)
self.assertRaises(TypeError, paddle.ldexp, paddle.to_tensor(x), y)


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