Skip to content

Commit 7832e9d

Browse files
olittlefacebook-github-bot
authored andcommitted
Add a bisect percentile operator (pytorch#10563)
Summary: Add a bisect percentile operators with lower and upper bounds for interpolation Pull Request resolved: pytorch#10563 Reviewed By: chocjy Differential Revision: D7802182 Pulled By: olittle fbshipit-source-id: 89ebfa8b3463adc2c89235fa3dfffa187a9d5417
1 parent 3d07574 commit 7832e9d

File tree

3 files changed

+441
-0
lines changed

3 files changed

+441
-0
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#include "caffe2/operators/bisect_percentile_op.h"
2+
3+
namespace caffe2 {
4+
5+
REGISTER_CPU_OPERATOR(BisectPercentile, BisectPercentileOp<CPUContext>);
6+
OPERATOR_SCHEMA(BisectPercentile)
7+
.NumInputs(1)
8+
.NumOutputs(1)
9+
.SetDoc(R"DOC(
10+
This operator is to map raw feature values into the percentile
11+
representations based on Bisection for more than one feature.
12+
13+
The input is the bath of input feature values, with the size of (batch_size,
14+
num_feature), where num_feature = F (F >= 1).
15+
16+
For each feature, we also need additional information regarding the feature
17+
value distribution.
18+
There are several vectors to keep data to percentile mappping information
19+
as arguments (context):
20+
1. feature raw values (R)
21+
2. feature percentile mapping (P)
22+
3. feature percentile lower bound (L)
23+
4. feature percentile upper bound (U)
24+
25+
A toy example:
26+
Suppose the sampled data distribution is as follows:
27+
1, 1, 2, 2, 2, 2, 2, 2, 3, 4
28+
We have the mapping vectors as follows:
29+
R = [1, 2, 3, 4]
30+
P = [0.15, 0.55, 0.9, 1.0]
31+
L = [0.1, 0.3, 0.9, 1.0]
32+
U = [0.2, 0.8, 0.9, 1.0]
33+
Where P is computed as (L + U) / 2.
34+
35+
For a given list of feature values, X = [x_0, x_1, ..., x_i, ...], for each
36+
feature value (x_i) we first apply bisection to find the right index (t),
37+
such that R[t] <= x_i < R[t+1].
38+
If x_i = R[t], P[t] is returned;
39+
otherwise, the interpolation is apply by (R[t], R[t+1]) and (U[t] and L[t]).
40+
41+
As there are F features (F >= 1), we concate all the R_f, P_f, L_f, and
42+
U_f for each feature f and use an additional input length to keep track of
43+
the number of points for each set of raw feature value to percentile mapping.
44+
For example, there are two features:
45+
R_1 =[0.1, 0.4, 0.5];
46+
R_2 = [0.3, 1.2];
47+
We will build R = [0.1, 0.4, 0.5, 0.3, 1.2]; besides, we have
48+
lengths = [3, 2]
49+
to indicate the boundries of the percentile information.
50+
51+
)DOC")
52+
.Arg(
53+
"percentile_raw",
54+
"1D tensor, which is the concatenation of all sorted raw feature "
55+
"values for all features.")
56+
.Arg(
57+
"percentile_mapping",
58+
"1D tensor. There is one-one mapping between percentile_mapping and "
59+
"percentile_raw such that each element in percentile_mapping "
60+
"corresponds to the percentile value of the corresponding raw feature "
61+
"value.")
62+
.Arg(
63+
"percentile_lower",
64+
"1D tensor. There is one-one mapping between percentile_upper and "
65+
"percentile_raw such that each element in percentile_mapping "
66+
"corresponds to the percentile lower bound of the corresponding raw "
67+
"feature value.")
68+
.Arg(
69+
"percentile_upper",
70+
"1D tensor. There is one-one mapping between percentile_upper and "
71+
"percentile_raw such that each element in percentile_mapping "
72+
"corresponds to the percentile upper bound of the corresponding raw "
73+
"feature value.")
74+
.Arg(
75+
"lengths",
76+
"1D tensor. There is one-one mapping between percentile_upper and "
77+
"percentile_raw such that each element in percentile_mapping "
78+
"corresponds to the percentile upper bound of the corresponding raw "
79+
"feature value.")
80+
.Input(
81+
0,
82+
"raw_values",
83+
"Input 2D tensor of floats of size (N, D), where N is the batch size "
84+
"and D is the feature dimension.")
85+
.Output(
86+
0,
87+
"percentile",
88+
"2D tensor of output with the same dimensions as the input raw_values.");
89+
90+
NO_GRADIENT(BisectPercentile);
91+
92+
} // namespace caffe2
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
#ifndef CAFFE2_OPERATORS_BISECT_PERCENTILE_OP_H_
2+
#define CAFFE2_OPERATORS_BISECT_PERCENTILE_OP_H_
3+
4+
#include "caffe2/core/context.h"
5+
#include "caffe2/core/logging.h"
6+
#include "caffe2/core/operator.h"
7+
#include "caffe2/core/tensor.h"
8+
#include "caffe2/utils/math.h"
9+
10+
namespace caffe2 {
11+
12+
template <class Context>
13+
class BisectPercentileOp final : public Operator<Context> {
14+
public:
15+
USE_OPERATOR_CONTEXT_FUNCTIONS;
16+
BisectPercentileOp(const OperatorDef& operator_def, Workspace* ws)
17+
: Operator<Context>(operator_def, ws),
18+
pct_raw_(OperatorBase::GetRepeatedArgument<float>(
19+
"percentile_raw",
20+
vector<float>{})),
21+
pct_mapping_(OperatorBase::GetRepeatedArgument<float>(
22+
"percentile_mapping",
23+
vector<float>{})),
24+
pct_lower_(OperatorBase::GetRepeatedArgument<float>(
25+
"percentile_lower",
26+
vector<float>{})),
27+
pct_upper_(OperatorBase::GetRepeatedArgument<float>(
28+
"percentile_upper",
29+
vector<float>{})),
30+
pct_lens_(
31+
OperatorBase::GetRepeatedArgument<int>("lengths", vector<int>{})) {
32+
CAFFE_ENFORCE_EQ(
33+
pct_raw_.size(),
34+
pct_mapping_.size(),
35+
"Feature (raw) data and percentile value dimension should match.");
36+
CAFFE_ENFORCE_EQ(
37+
pct_raw_.size(),
38+
pct_lower_.size(),
39+
"Feature (raw) data and lower bound dimension should match.");
40+
CAFFE_ENFORCE_EQ(
41+
pct_raw_.size(),
42+
pct_upper_.size(),
43+
"Feature (raw) data and upper bound dimension should match.");
44+
n_features = pct_lens_.size();
45+
index.reserve(n_features + 1);
46+
index[0] = 0;
47+
for (int i = 1; i <= n_features; ++i) {
48+
index[i] = index[i - 1] + pct_lens_[i - 1];
49+
}
50+
CAFFE_ENFORCE_EQ(
51+
index[n_features], // The sum of lengths_data
52+
pct_raw_.size(),
53+
"Sum of lengths should be equal to the total number of percentile "
54+
"mapping data samples");
55+
}
56+
57+
bool RunOnDevice() override {
58+
// Input
59+
const auto& raw = Input(RAW);
60+
CAFFE_ENFORCE_EQ(raw.ndim(), 2);
61+
const auto batch_size = raw.dim(0);
62+
const auto num_features = raw.dim(1);
63+
CAFFE_ENFORCE_EQ(num_features, pct_lens_.size());
64+
const float* raw_data = raw.template data<float>();
65+
66+
// Output
67+
auto* pct = Output(PCT);
68+
pct->ResizeLike(raw);
69+
float* pct_output = pct->template mutable_data<float>();
70+
71+
// Compute percentile for each raw feature value
72+
int feature_start_index = 0;
73+
int feature_length = 0;
74+
int cur_index = 0;
75+
76+
for (int i = 0; i < num_features; ++i) {
77+
cur_index = i;
78+
feature_start_index = index[i];
79+
feature_length = pct_lens_[i];
80+
for (int j = 0; j < batch_size; ++j) {
81+
pct_output[cur_index] = compute_percentile(
82+
pct_raw_.begin() + feature_start_index,
83+
pct_mapping_.begin() + feature_start_index,
84+
pct_lower_.begin() + feature_start_index,
85+
pct_upper_.begin() + feature_start_index,
86+
feature_length,
87+
raw_data[cur_index]);
88+
cur_index += num_features;
89+
}
90+
}
91+
return true;
92+
}
93+
94+
protected:
95+
INPUT_TAGS(RAW);
96+
OUTPUT_TAGS(PCT);
97+
98+
private:
99+
int n_features;
100+
vector<float> pct_raw_;
101+
vector<float> pct_mapping_;
102+
vector<float> pct_lower_;
103+
vector<float> pct_upper_;
104+
vector<int> pct_lens_;
105+
vector<int> index;
106+
vector<std::map<float, float>> fast_pct;
107+
108+
const float kEPSILON = 1e-10;
109+
110+
int binary_search(
111+
const std::vector<float>::iterator& data,
112+
int lo,
113+
int hi,
114+
float val) {
115+
int mid;
116+
bool low_cond, high_cond;
117+
118+
while (lo < hi) {
119+
mid = (lo + hi) >> 1;
120+
low_cond = (data[mid] <= val);
121+
high_cond = (val < data[mid + 1]);
122+
if (low_cond && high_cond) {
123+
return mid;
124+
} else if (!low_cond) {
125+
hi = mid - 1;
126+
} else {
127+
lo = mid + 1;
128+
}
129+
}
130+
return lo;
131+
}
132+
133+
float compute_percentile(
134+
const std::vector<float>::iterator& pct_raw_it,
135+
const std::vector<float>::iterator& pct_mapping_it,
136+
const std::vector<float>::iterator& pct_lower_it,
137+
const std::vector<float>::iterator& pct_upper_it,
138+
const int size,
139+
const float val) {
140+
// Corner cases where no interpolation is needed.
141+
if (val < pct_raw_it[0]) {
142+
return 0.;
143+
}
144+
if (val > pct_raw_it[size - 1]) {
145+
return 1.;
146+
}
147+
148+
float result;
149+
// Interpolation by binary search
150+
const auto k = binary_search(pct_raw_it, 0, size - 1, val);
151+
152+
if (pct_raw_it[k] == val) {
153+
// Exact match
154+
result = pct_mapping_it[k];
155+
} else {
156+
// interpolation
157+
float w = (val - pct_raw_it[k]) /
158+
(pct_raw_it[k + 1] - pct_raw_it[k] + kEPSILON);
159+
result = (1 - w) * pct_upper_it[k] + w * pct_lower_it[k + 1];
160+
}
161+
return result;
162+
}
163+
};
164+
165+
} // namespace caffe2
166+
167+
#endif // CAFFE2_OPERATORS_BISECT_PERCENTILE_OP_H_

0 commit comments

Comments
 (0)