Skip to content

Commit 9f731a6

Browse files
committed
Add math operator patches
Users can use `a+b`, `a*10`.
1 parent c73f00f commit 9f731a6

File tree

4 files changed

+347
-0
lines changed

4 files changed

+347
-0
lines changed

python/paddle/v2/fluid/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from memory_optimization_transpiler import memory_optimize
3737

3838
Tensor = LoDTensor
39+
3940
__all__ = framework.__all__ + executor.__all__ + [
4041
'io',
4142
'initializer',
@@ -93,4 +94,5 @@ def __bootstrap__():
9394
core.init_devices()
9495

9596

97+
layers.monkey_patch_variable()
9698
__bootstrap__()

python/paddle/v2/fluid/layers/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
from control_flow import *
2424
import device
2525
from device import *
26+
import math_op_patch
27+
from math_op_patch import *
2628

2729
__all__ = []
2830
__all__ += nn.__all__
@@ -31,3 +33,4 @@
3133
__all__ += control_flow.__all__
3234
__all__ += ops.__all__
3335
__all__ += device.__all__
36+
__all__ += math_op_patch.__all__
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# Copyright (c) 2018 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+
from ..framework import Variable, unique_name
16+
from ..registry import OpProtoHolder
17+
18+
__all__ = ['monkey_patch_variable']
19+
20+
21+
def monkey_patch_variable():
22+
def new_name():
23+
return unique_name("tmp")
24+
25+
def safe_get_dtype(var):
26+
try:
27+
dtype = var.dtype
28+
except:
29+
raise ValueError("Cannot get data type from %s", var.name)
30+
return dtype
31+
32+
def create_scalar(block, value, dtype):
33+
value = float(value)
34+
tmp_name = new_name()
35+
var = block.create_var(name=tmp_name, shape=[1], dtype=dtype)
36+
block.append_op(
37+
type="fill",
38+
outputs={"Out": [var]},
39+
attrs={"value": [value],
40+
"shape": [1],
41+
"dtype": dtype})
42+
return var
43+
44+
def create_tensor(block, value, dtype, shape):
45+
value = float(value)
46+
tmp_name = new_name()
47+
var = block.create_var(name=tmp_name, shape=shape, dtype=dtype)
48+
block.append_op(
49+
type="fill_constant",
50+
outputs={'Out': [var]},
51+
attrs={'dtype': var.dtype,
52+
'shape': shape,
53+
'value': value})
54+
return var
55+
56+
def create_tensor_with_batchsize(ref_var, value, dtype):
57+
assert isinstance(ref_var, Variable)
58+
value = float(value)
59+
tmp_name = new_name()
60+
var = ref_var.block.create_var(name=tmp_name, dtype=dtype)
61+
ref_var.block.append_op(
62+
type='fill_constant_batch_size_like',
63+
outputs={'Out': [var]},
64+
inputs={'Input': [ref_var]},
65+
attrs={'shape': ref_var.shape,
66+
'value': value})
67+
return var
68+
69+
def astype(self, dtype):
70+
"""
71+
Cast a variable to data type.
72+
NOTE: The variable must be a Tensor
73+
Args:
74+
self(Variable): The source variable
75+
dtype: The target dtype
76+
77+
Returns:
78+
Variable with new dtype
79+
"""
80+
tmp_name = new_name()
81+
out = self.block.create_var(name=tmp_name, dtype=dtype)
82+
self.block.append_op(
83+
type="cast",
84+
inputs={"X": [self]},
85+
outputs={"Out": [out]},
86+
attrs={"in_dtype": self.dtype,
87+
"out_dtype": out.dtype})
88+
return out
89+
90+
def _elemwise_method_creator_(method_name, op_type, reverse=False):
91+
def __impl__(self, other_var):
92+
lhs_dtype = safe_get_dtype(self)
93+
94+
if not isinstance(other_var, Variable):
95+
if reverse:
96+
has_batch_size = False
97+
for elem in self.shape:
98+
if elem < 0:
99+
has_batch_size = True
100+
break
101+
if not has_batch_size:
102+
other_var = create_tensor(
103+
self.block,
104+
other_var,
105+
dtype=lhs_dtype,
106+
shape=self.shape)
107+
else:
108+
other_var = create_tensor_with_batchsize(
109+
self, other_var, lhs_dtype)
110+
else:
111+
# add fill_op to self.block
112+
other_var = create_scalar(
113+
self.block, value=other_var, dtype=lhs_dtype)
114+
115+
rhs_dtype = safe_get_dtype(other_var)
116+
if lhs_dtype != rhs_dtype:
117+
other_var = astype(other_var, lhs_dtype)
118+
if reverse:
119+
tmp = self
120+
self = other_var
121+
other_var = tmp
122+
123+
tmp_name = new_name()
124+
out = self.block.create_var(name=tmp_name, dtype=lhs_dtype)
125+
self.block.append_op(
126+
type=op_type,
127+
inputs={'X': [self],
128+
'Y': [other_var]},
129+
outputs={'Out': out})
130+
return out
131+
132+
comment = OpProtoHolder.instance().get_op_proto(op_type).comment
133+
134+
__impl__.__doc__ = """
135+
{0}
136+
Args:
137+
self(Variable): left hand variable
138+
other_var(Variable|float|int): right hand variable
139+
140+
Returns:
141+
Variable
142+
""".format(comment)
143+
__impl__.__name__ = method_name
144+
return __impl__
145+
146+
# inject methods
147+
for method_name, op_type, reverse in (
148+
("__add__", "elementwise_add", False),
149+
# a+b == b+a. Do not need to reverse explicitly
150+
("__radd__", "elementwise_add", False),
151+
("__sub__", "elementwise_sub", False),
152+
("__rsub__", "elementwise_sub", True),
153+
("__mul__", "elementwise_mul", False),
154+
# a*b == b*a. Do not need to reverse explicitly
155+
("__rmul__", "elementwise_mul", False),
156+
("__div__", "elementwise_div", False),
157+
("__rdiv__", "elementwise_div", True)):
158+
setattr(Variable, method_name,
159+
_elemwise_method_creator_(method_name, op_type, reverse))
160+
161+
Variable.astype = astype
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# Copyright (c) 2018 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+
import unittest
16+
import decorators
17+
import paddle.v2.fluid as fluid
18+
import numpy
19+
20+
21+
class TestMathOpPatches(unittest.TestCase):
22+
@decorators.prog_scope()
23+
def test_add_scalar(self):
24+
a = fluid.layers.data(name="a", shape=[1])
25+
b = a + 10
26+
place = fluid.CPUPlace()
27+
exe = fluid.Executor(place)
28+
a_np = numpy.random.random(size=[10, 1]).astype('float32')
29+
b_np = exe.run(fluid.default_main_program(),
30+
feed={"a": a_np},
31+
fetch_list=[b])
32+
self.assertTrue(numpy.allclose(a_np + 10, b_np))
33+
34+
@decorators.prog_scope()
35+
def test_radd_scalar(self):
36+
a = fluid.layers.data(name="a", shape=[1])
37+
b = 10 + a
38+
place = fluid.CPUPlace()
39+
exe = fluid.Executor(place)
40+
a_np = numpy.random.random(size=[10, 1]).astype('float32')
41+
b_np = exe.run(fluid.default_main_program(),
42+
feed={"a": a_np},
43+
fetch_list=[b])
44+
self.assertTrue(numpy.allclose(a_np + 10, b_np))
45+
46+
@decorators.prog_scope()
47+
def test_sub_scalar(self):
48+
a = fluid.layers.data(name="a", shape=[1])
49+
b = a - 10
50+
place = fluid.CPUPlace()
51+
exe = fluid.Executor(place)
52+
a_np = numpy.random.random(size=[10, 1]).astype('float32')
53+
b_np = exe.run(fluid.default_main_program(),
54+
feed={"a": a_np},
55+
fetch_list=[b])
56+
self.assertTrue(numpy.allclose(a_np - 10, b_np))
57+
58+
@decorators.prog_scope()
59+
def test_radd_scalar(self):
60+
a = fluid.layers.data(name="a", shape=[1])
61+
b = 10 - a
62+
place = fluid.CPUPlace()
63+
exe = fluid.Executor(place)
64+
a_np = numpy.random.random(size=[10, 1]).astype('float32')
65+
b_np = exe.run(fluid.default_main_program(),
66+
feed={"a": a_np},
67+
fetch_list=[b])
68+
self.assertTrue(numpy.allclose(10 - a_np, b_np))
69+
70+
@decorators.prog_scope()
71+
def test_mul_scalar(self):
72+
a = fluid.layers.data(name="a", shape=[1])
73+
b = a * 10
74+
place = fluid.CPUPlace()
75+
exe = fluid.Executor(place)
76+
a_np = numpy.random.random(size=[10, 1]).astype('float32')
77+
b_np = exe.run(fluid.default_main_program(),
78+
feed={"a": a_np},
79+
fetch_list=[b])
80+
self.assertTrue(numpy.allclose(a_np * 10, b_np))
81+
82+
@decorators.prog_scope()
83+
def test_rmul_scalar(self):
84+
a = fluid.layers.data(name="a", shape=[1])
85+
b = 10 * a
86+
place = fluid.CPUPlace()
87+
exe = fluid.Executor(place)
88+
a_np = numpy.random.random(size=[10, 1]).astype('float32')
89+
b_np = exe.run(fluid.default_main_program(),
90+
feed={"a": a_np},
91+
fetch_list=[b])
92+
self.assertTrue(numpy.allclose(10 * a_np, b_np))
93+
94+
@decorators.prog_scope()
95+
def test_div_scalar(self):
96+
a = fluid.layers.data(name="a", shape=[1])
97+
b = a / 10
98+
place = fluid.CPUPlace()
99+
exe = fluid.Executor(place)
100+
a_np = numpy.random.random(size=[10, 1]).astype('float32')
101+
b_np = exe.run(fluid.default_main_program(),
102+
feed={"a": a_np},
103+
fetch_list=[b])
104+
self.assertTrue(numpy.allclose(a_np / 10, b_np))
105+
106+
@decorators.prog_scope()
107+
def test_rdiv_scalar(self):
108+
a = fluid.layers.data(name="a", shape=[1])
109+
b = 10 / a
110+
place = fluid.CPUPlace()
111+
exe = fluid.Executor(place)
112+
a_np = numpy.random.random(size=[10, 1]).astype('float32') + 1e-2
113+
114+
b_np = exe.run(fluid.default_main_program(),
115+
feed={"a": a_np},
116+
fetch_list=[b])
117+
self.assertTrue(numpy.allclose(10 / a_np, b_np))
118+
119+
@decorators.prog_scope()
120+
def test_div_two_tensor(self):
121+
a = fluid.layers.data(name="a", shape=[1])
122+
b = fluid.layers.data(name="b", shape=[1])
123+
c = a / b
124+
place = fluid.CPUPlace()
125+
exe = fluid.Executor(place)
126+
a_np = numpy.random.random(size=[10, 1]).astype('float32')
127+
b_np = numpy.random.random(size=[10, 1]).astype('float32') + 1e-2
128+
c_np = exe.run(fluid.default_main_program(),
129+
feed={"a": a_np,
130+
'b': b_np},
131+
fetch_list=[c])
132+
self.assertTrue(numpy.allclose(a_np / b_np, c_np))
133+
134+
@decorators.prog_scope()
135+
def test_mul_two_tensor(self):
136+
a = fluid.layers.data(name="a", shape=[1])
137+
b = fluid.layers.data(name="b", shape=[1])
138+
c = a * b
139+
place = fluid.CPUPlace()
140+
exe = fluid.Executor(place)
141+
a_np = numpy.random.random(size=[10, 1]).astype('float32')
142+
b_np = numpy.random.random(size=[10, 1]).astype('float32')
143+
c_np = exe.run(fluid.default_main_program(),
144+
feed={"a": a_np,
145+
'b': b_np},
146+
fetch_list=[c])
147+
self.assertTrue(numpy.allclose(a_np * b_np, c_np))
148+
149+
@decorators.prog_scope()
150+
def test_add_two_tensor(self):
151+
a = fluid.layers.data(name="a", shape=[1])
152+
b = fluid.layers.data(name="b", shape=[1])
153+
c = a + b
154+
place = fluid.CPUPlace()
155+
exe = fluid.Executor(place)
156+
a_np = numpy.random.random(size=[10, 1]).astype('float32')
157+
b_np = numpy.random.random(size=[10, 1]).astype('float32')
158+
c_np = exe.run(fluid.default_main_program(),
159+
feed={"a": a_np,
160+
'b': b_np},
161+
fetch_list=[c])
162+
self.assertTrue(numpy.allclose(a_np + b_np, c_np))
163+
164+
@decorators.prog_scope()
165+
def test_sub_two_tensor(self):
166+
a = fluid.layers.data(name="a", shape=[1])
167+
b = fluid.layers.data(name="b", shape=[1])
168+
c = a - b
169+
place = fluid.CPUPlace()
170+
exe = fluid.Executor(place)
171+
a_np = numpy.random.random(size=[10, 1]).astype('float32')
172+
b_np = numpy.random.random(size=[10, 1]).astype('float32')
173+
c_np = exe.run(fluid.default_main_program(),
174+
feed={"a": a_np,
175+
'b': b_np},
176+
fetch_list=[c])
177+
self.assertTrue(numpy.allclose(a_np - b_np, c_np))
178+
179+
180+
if __name__ == '__main__':
181+
unittest.main()

0 commit comments

Comments
 (0)