Skip to content

Commit a1c0b24

Browse files
authored
Correct CPU gradients of the argsort op. [cherry-pick #22739] test=release/1.7 (#22843)
1 parent cfa34df commit a1c0b24

File tree

2 files changed

+222
-64
lines changed

2 files changed

+222
-64
lines changed

paddle/fluid/operators/argsort_op.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,13 @@ static void FullAssign(Type input_height, Type input_width, int input_dim,
8181
auto e_input = EigenVector<T>::Flatten(*input);
8282
auto e_indices = EigenVector<Type>::Flatten(*indices);
8383
for (Type j = 0; j < input_width; ++j) {
84-
t_out[i * input_width + e_indices(j)] = e_input(e_indices(j));
84+
t_out[i * input_width + e_indices(j)] = e_input(j);
8585
}
8686
} else {
8787
auto e_input = EigenMatrix<T>::Reshape(*input, input_dim - 1);
8888
auto e_indices = EigenMatrix<Type>::Reshape(*indices, input_dim - 1);
8989
for (Type j = 0; j < input_width; ++j) {
90-
t_out[i * input_width + e_indices(i, j)] = e_input(i, e_indices(i, j));
90+
t_out[i * input_width + e_indices(i, j)] = e_input(i, j);
9191
}
9292
}
9393
}

python/paddle/fluid/tests/unittests/test_argsort_op.py

Lines changed: 220 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
1+
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.
@@ -15,34 +15,176 @@
1515
from __future__ import print_function
1616

1717
import unittest
18+
import paddle.fluid as fluid
19+
import paddle.fluid.layers as layers
1820
import numpy as np
19-
from op_test import OpTest
21+
import six
2022
import paddle.fluid.core as core
2123

24+
from paddle.fluid import ParamAttr
25+
from paddle.fluid.framework import Program, grad_var_name
26+
from paddle.fluid.executor import Executor
27+
from paddle.fluid.backward import append_backward
2228

23-
class TestArgsortOp(OpTest):
24-
def setUp(self):
25-
self.init_axis()
26-
self.init_datatype()
27-
self.init_direction()
28-
x = np.random.random((2, 3, 4, 5, 10)).astype(self.dtype)
29-
self.attrs = {'axis': self.axis, 'descending': self.descending}
30-
if self.axis < 0:
31-
self.axis = self.axis + len(x.shape)
29+
np.random.seed(123)
30+
31+
32+
class PyArgsort(object):
33+
def __init__(self, input_shape, axis, descending, dtype):
34+
self.x = np.random.random(input_shape).astype(dtype)
35+
self.label = np.random.random(input_shape).astype(dtype)
36+
if axis < 0:
37+
self.axis = axis + len(self.x.shape)
38+
else:
39+
self.axis = axis
40+
self.descending = descending
41+
42+
def forward(self):
3243
if self.descending:
3344
self.indices = np.flip(
3445
np.argsort(
35-
x, kind='quicksort', axis=self.axis), self.axis)
36-
self.out = np.flip(
46+
self.x, kind='quicksort', axis=self.axis), self.axis)
47+
self.sorted_x = np.flip(
3748
np.sort(
38-
x, kind='quicksort', axis=self.axis), self.axis)
49+
self.x, kind='quicksort', axis=self.axis), self.axis)
3950
else:
40-
self.indices = np.argsort(x, kind='quicksort', axis=self.axis)
41-
self.out = np.sort(x, kind='quicksort', axis=self.axis)
51+
self.indices = np.argsort(self.x, kind='quicksort', axis=self.axis)
52+
self.sorted_x = np.sort(self.x, kind='quicksort', axis=self.axis)
53+
self.loss = self.sorted_x * self.label
54+
self.loss = np.sum(self.loss)
55+
out = (np.array(
56+
self.indices, dtype=self.indices.dtype), np.array(
57+
self.sorted_x, dtype=self.sorted_x.dtype), np.array(
58+
[self.loss], dtype=self.loss.dtype))
59+
return out
4260

43-
self.op_type = "argsort"
44-
self.inputs = {'X': x}
45-
self.outputs = {'Indices': self.indices, 'Out': self.out}
61+
62+
def create_tensor(np_data, place):
63+
tensor = core.LoDTensor()
64+
tensor.set(np_data, place)
65+
return tensor
66+
67+
68+
class TestArgsortOpCPU(unittest.TestCase):
69+
def setup_program(self):
70+
self.main_program = Program()
71+
self.startup_program = Program()
72+
self.init_place()
73+
74+
def setUp(self):
75+
self.init_axis()
76+
self.init_datatype()
77+
self.init_direction()
78+
self.init_inputshape()
79+
80+
self.setup_program()
81+
self.feed_data_field = {"x", "label"}
82+
self.grad_data_field = {"x"}
83+
84+
self.py_argsort = PyArgsort(self.input_shape, self.axis,
85+
self.descending, self.dtype)
86+
87+
with fluid.program_guard(self.main_program, self.startup_program):
88+
x = fluid.layers.data(
89+
name="x", shape=self.input_shape, dtype=self.dtype)
90+
x.stop_gradient = False
91+
label = fluid.layers.data(
92+
name="label", shape=self.input_shape, dtype=self.dtype)
93+
self.sorted_x, self.index = fluid.layers.argsort(
94+
input=x, axis=self.axis, descending=self.descending)
95+
self.sorted_x.stop_gradient = False
96+
loss = fluid.layers.elementwise_mul(self.sorted_x, label)
97+
self.loss = fluid.layers.reduce_sum(loss)
98+
99+
def forward(self):
100+
self.feed_map = {
101+
x: create_tensor(getattr(self.py_argsort, x), self.place)
102+
for x in self.feed_data_field
103+
}
104+
exe = Executor(self.place)
105+
out = exe.run(self.main_program,
106+
feed=self.feed_map,
107+
fetch_list=[self.index, self.sorted_x, self.loss])
108+
return out
109+
110+
def backward(self):
111+
self.feed_map = {
112+
x: create_tensor(getattr(self.py_argsort, x), self.place)
113+
for x in self.feed_data_field
114+
}
115+
fetch_list = [
116+
self.main_program.global_block().var(grad_var_name(x))
117+
for x in self.grad_data_field
118+
]
119+
exe = Executor(self.place)
120+
out = exe.run(self.main_program,
121+
feed=self.feed_map,
122+
fetch_list=fetch_list,
123+
return_numpy=False)
124+
return out
125+
126+
def test_backward(self, numeric_grad_delta=1e-5, max_relative_error=1e-7):
127+
self.check_forward()
128+
129+
with fluid.program_guard(self.main_program, self.startup_program):
130+
append_backward(self.loss)
131+
132+
ana_grad = [np.array(x) for x in self.backward()]
133+
134+
num_grad = self.get_numerical_gradient(delta=numeric_grad_delta)
135+
self.assert_is_close(
136+
num_grad,
137+
ana_grad,
138+
'x',
139+
max_relative_error=max_relative_error,
140+
msg_prefix="Gradient Check On %s" % str(self.place))
141+
142+
def check_forward(self):
143+
pd_outputs = self.forward()
144+
py_outputs = self.py_argsort.forward()
145+
for pd_output, py_output in zip(pd_outputs, py_outputs):
146+
self.assertEqual(pd_output.shape, py_output.shape)
147+
self.assertTrue(
148+
np.allclose(
149+
pd_output, py_output, atol=0, equal_nan=False))
150+
151+
def get_numerical_gradient(self, delta=1e-7):
152+
if self.dtype == 'float16':
153+
delta = np.array(delta).astype(np.float16)
154+
feed_list = [getattr(self.py_argsort, x) for x in self.grad_data_field]
155+
grad_list = [np.zeros_like(x) for x in feed_list]
156+
for feed, grad in zip(feed_list, grad_list):
157+
for f, g in np.nditer([feed, grad], op_flags=['readwrite']):
158+
o = float(f)
159+
f[...] = o + delta
160+
y_pos = self.forward()[2]
161+
162+
f[...] = o - delta
163+
y_neg = self.forward()[2]
164+
165+
f[...] = o
166+
dout_dfeed = (y_pos - y_neg) / (delta * 2)
167+
g[...] = dout_dfeed[0]
168+
169+
return grad_list
170+
171+
def assert_is_close(self, numeric_grads, analytic_grads, names,
172+
max_relative_error, msg_prefix):
173+
for a, b, name in six.moves.zip(numeric_grads, analytic_grads, names):
174+
abs_a = np.abs(a)
175+
abs_a[abs_a < 1e-3] = 1
176+
177+
diff_mat = np.abs(a - b) / abs_a
178+
max_diff = np.max(diff_mat)
179+
180+
def err_msg():
181+
offset = np.argmax(diff_mat > max_relative_error)
182+
return ("%s error, %s variable %s max gradient diff %f over limit %f, "
183+
"the first error element is %d, expected %f, but got %f.") \
184+
% ('argsort', msg_prefix, name, max_diff, max_relative_error,
185+
offset, a.flatten()[offset], b.flatten()[offset])
186+
187+
self.assertLessEqual(max_diff, max_relative_error, err_msg())
46188

47189
def init_axis(self):
48190
self.axis = -1
@@ -53,111 +195,127 @@ def init_datatype(self):
53195
def init_direction(self):
54196
self.descending = False
55197

56-
def test_check_output(self):
57-
self.check_output()
198+
def init_inputshape(self):
199+
self.input_shape = (2, 2, 2, 2, 3)
200+
201+
def init_place(self):
202+
self.place = core.CPUPlace()
58203

59-
def test_check_grad(self):
60-
self.check_grad(['X'], 'Out')
61204

205+
class TestArgsortOpGPU(TestArgsortOpCPU):
206+
def init_place(self):
207+
if core.is_compiled_with_cuda():
208+
self.place = core.CUDAPlace(0)
209+
else:
210+
self.place = core.CPUPlace()
62211

63-
class TestArgsortOpAxis0(TestArgsortOp):
212+
213+
class TestArgsortOpAxis0CPU(TestArgsortOpCPU):
64214
def init_axis(self):
65215
self.axis = 0
66216

67217

68-
class TestArgsortOpAxis1(TestArgsortOp):
218+
class TestArgsortOpAxis0GPU(TestArgsortOpGPU):
69219
def init_axis(self):
70-
self.axis = 1
220+
self.axis = 0
71221

72222

73-
class TestArgsortOpAxis2(TestArgsortOp):
223+
class TestArgsortOpAxis1CPU(TestArgsortOpCPU):
74224
def init_axis(self):
75-
self.axis = 2
225+
self.axis = 1
76226

77227

78-
class TestArgsortOpAxisNeg1(TestArgsortOp):
228+
class TestArgsortOpAxis1GPU(TestArgsortOpGPU):
79229
def init_axis(self):
80-
self.axis = -1
230+
self.axis = 1
81231

82232

83-
class TestArgsortOpAxisNeg2(TestArgsortOp):
233+
class TestArgsortOpAxis2CPU(TestArgsortOpCPU):
84234
def init_axis(self):
85-
self.axis = -2
235+
self.axis = 2
86236

87237

88-
class TestArgsortOpFP16(TestArgsortOp):
89-
def init_datatype(self):
90-
if core.is_compiled_with_cuda():
91-
self.dtype = 'float16'
238+
class TestArgsortOpAxis2GPU(TestArgsortOpGPU):
239+
def init_axis(self):
240+
self.axis = 2
92241

93-
def test_check_output(self):
94-
pass
95242

96-
def test_check_output_with_place(self):
97-
if core.is_compiled_with_cuda():
98-
place = core.CUDAPlace(0)
99-
self.check_output_with_place(place, atol=1e-5)
243+
class TestArgsortOpAxisNeg1CPU(TestArgsortOpCPU):
244+
def init_axis(self):
245+
self.axis = -1
100246

101247

102-
class TestArgsortOpFP16Axis0(TestArgsortOpFP16):
248+
class TestArgsortOpAxisNeg1GPU(TestArgsortOpGPU):
103249
def init_axis(self):
104-
self.axis = 0
250+
self.axis = -1
105251

106252

107-
class TestArgsortOpFP16Axis2(TestArgsortOpFP16):
253+
class TestArgsortOpAxisNeg2CPU(TestArgsortOpCPU):
108254
def init_axis(self):
109-
self.axis = 2
255+
self.axis = -2
110256

111257

112-
class TestArgsortOpFP16AxisNeg2(TestArgsortOpFP16):
258+
class TestArgsortOpAxisNeg2GPU(TestArgsortOpGPU):
113259
def init_axis(self):
114260
self.axis = -2
115261

116262

117-
class TestArgsortOpFP16Axis4Neg4(TestArgsortOpFP16):
118-
def init_axis(self):
119-
self.axis = -4
263+
class TestArgsortOpDescendingAxisCPU(TestArgsortOpCPU):
264+
def init_direction(self):
265+
self.descending = True
120266

121267

122-
class TestArgsortOpDescendingAxis(TestArgsortOp):
268+
class TestArgsortOpDescendingAxisGPU(TestArgsortOpGPU):
123269
def init_direction(self):
124270
self.descending = True
125271

126272

127-
class TestArgsortOpDescendingAxis0(TestArgsortOpAxis0):
273+
class TestArgsortOpDescendingAxis0CPU(TestArgsortOpAxis0CPU):
128274
def init_direction(self):
129275
self.descending = True
130276

131277

132-
class TestArgsortOpDescendingAxis1(TestArgsortOpAxis1):
278+
class TestArgsortOpDescendingAxis0GPU(TestArgsortOpAxis0GPU):
133279
def init_direction(self):
134280
self.descending = True
135281

136282

137-
class TestArgsortOpDescendingAxis2(TestArgsortOpAxis2):
283+
class TestArgsortOpDescendingAxis1CPU(TestArgsortOpAxis1CPU):
138284
def init_direction(self):
139285
self.descending = True
140286

141287

142-
class TestArgsortOpDescendingAxisNeg1(TestArgsortOpAxisNeg1):
288+
class TestArgsortOpDescendingAxis1GPU(TestArgsortOpAxis1GPU):
143289
def init_direction(self):
144290
self.descending = True
145291

146292

147-
class TestArgsortOpDescendingAxisNeg2(TestArgsortOpAxisNeg2):
293+
class TestArgsortOpDescendingAxis2CPU(TestArgsortOpAxis2CPU):
148294
def init_direction(self):
149295
self.descending = True
150296

151297

152-
class TestArgsortOpFP32Axis(TestArgsortOp):
153-
def init_datatype(self):
154-
self.dtype = "float32"
298+
class TestArgsortOpDescendingAxis2GPU(TestArgsortOpAxis2GPU):
299+
def init_direction(self):
300+
self.descending = True
155301

156302

157-
class TestArgsortOpFP32DescendingAxis(TestArgsortOp):
158-
def init_datatype(self):
159-
self.dtype = "float32"
303+
class TestArgsortOpDescendingAxisNeg1CPU(TestArgsortOpAxisNeg1CPU):
304+
def init_direction(self):
305+
self.descending = True
306+
307+
308+
class TestArgsortOpDescendingAxisNeg1GPU(TestArgsortOpAxisNeg1GPU):
309+
def init_direction(self):
310+
self.descending = True
311+
312+
313+
class TestArgsortOpDescendingAxisNeg2CPU(TestArgsortOpAxisNeg2CPU):
314+
def init_direction(self):
315+
self.descending = True
316+
160317

318+
class TestArgsortOpDescendingAxisNeg2GPU(TestArgsortOpAxisNeg2GPU):
161319
def init_direction(self):
162320
self.descending = True
163321

0 commit comments

Comments
 (0)