Skip to content

Commit d8326ec

Browse files
authored
Merge pull request #16741 from colourful-tree/dev (#16918)
add continuous value model op
1 parent b73f913 commit d8326ec

File tree

5 files changed

+359
-0
lines changed

5 files changed

+359
-0
lines changed

paddle/fluid/API.spec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ paddle.fluid.layers.tree_conv (ArgSpec(args=['nodes_vector', 'edge_set', 'output
238238
paddle.fluid.layers.npair_loss (ArgSpec(args=['anchor', 'positive', 'labels', 'l2_reg'], varargs=None, keywords=None, defaults=(0.002,)), ('document', '46994d10276dd4cb803b4062b5d14329'))
239239
paddle.fluid.layers.pixel_shuffle (ArgSpec(args=['x', 'upscale_factor'], varargs=None, keywords=None, defaults=None), ('document', '731b21c62a4add60a33bd76d802ffc5c'))
240240
paddle.fluid.layers.fsp_matrix (ArgSpec(args=['x', 'y'], varargs=None, keywords=None, defaults=None), ('document', 'b76ccca3735bea4a58a0dbf0d77c5393'))
241+
paddle.fluid.layers.continuous_value_model (ArgSpec(args=['input', 'cvm', 'use_cvm'], varargs=None, keywords=None, defaults=(True,)), ('document', 'a07a44c2bacdcd09c1f5f35a96a0514e'))
241242
paddle.fluid.layers.data (ArgSpec(args=['name', 'shape', 'append_batch_size', 'dtype', 'lod_level', 'type', 'stop_gradient'], varargs=None, keywords=None, defaults=(True, 'float32', 0, VarType.LOD_TENSOR, True)), ('document', '33bbd42027d872b3818b3d64ec52e139'))
242243
paddle.fluid.layers.open_files (ArgSpec(args=['filenames', 'shapes', 'lod_levels', 'dtypes', 'thread_num', 'buffer_size', 'pass_num', 'is_test'], varargs=None, keywords=None, defaults=(None, None, 1, None)), ('document', 'b1ae2e1cc0750e58726374061ea90ecc'))
243244
paddle.fluid.layers.read_file (ArgSpec(args=['reader'], varargs=None, keywords=None, defaults=None), ('document', 'b0a1c2fc51c27a106da28f3308c41f5e'))

paddle/fluid/operators/cvm_op.cc

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/* Copyright (c) 2019 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+
#include "paddle/fluid/operators/cvm_op.h"
16+
#include <memory>
17+
#include "paddle/fluid/operators/math/math_function.h"
18+
19+
namespace paddle {
20+
namespace operators {
21+
22+
using Tensor = framework::Tensor;
23+
24+
class CVMOp : public framework::OperatorWithKernel {
25+
public:
26+
using framework::OperatorWithKernel::OperatorWithKernel;
27+
28+
void InferShape(framework::InferShapeContext* ctx) const override {
29+
PADDLE_ENFORCE(ctx->HasInput("X"), "Input(X) should be not null.");
30+
PADDLE_ENFORCE(ctx->HasInput("CVM"), "Input(CVM) should be not null.");
31+
PADDLE_ENFORCE(ctx->HasOutput("Y"), "Output(Y) should be not null.");
32+
33+
auto x_dims = ctx->GetInputDim("X");
34+
auto cvm_dims = ctx->GetInputDim("CVM");
35+
PADDLE_ENFORCE_EQ(x_dims.size(), 2UL, "Input(X)'s rank should be 2.");
36+
PADDLE_ENFORCE_EQ(cvm_dims.size(), 2UL, "Input(CVM)'s rank should be 2.");
37+
PADDLE_ENFORCE_EQ(cvm_dims[1], 2UL,
38+
"The 2nd dimension of "
39+
"Input(CVM) should be 2.");
40+
41+
if (ctx->Attrs().Get<bool>("use_cvm")) {
42+
ctx->SetOutputDim("Y", {x_dims[0], x_dims[1]});
43+
} else {
44+
ctx->SetOutputDim("Y", {x_dims[0], x_dims[1] - 2});
45+
}
46+
ctx->ShareLoD("X", /*->*/ "Y");
47+
}
48+
49+
protected:
50+
// Explicitly set that the data type of computation kernel of
51+
// cvm
52+
// is determined by its input "X".
53+
framework::OpKernelType GetExpectedKernelType(
54+
const framework::ExecutionContext& ctx) const override {
55+
return framework::OpKernelType(ctx.Input<Tensor>("X")->type(),
56+
platform::CPUPlace());
57+
}
58+
};
59+
60+
class CVMGradientOp : public framework::OperatorWithKernel {
61+
public:
62+
using framework::OperatorWithKernel::OperatorWithKernel;
63+
64+
void InferShape(framework::InferShapeContext* ctx) const override {
65+
PADDLE_ENFORCE(ctx->HasInput("X"), "Input(X) should be not null.");
66+
PADDLE_ENFORCE(ctx->HasInput("CVM"), "Input(CVM) should be not null.");
67+
PADDLE_ENFORCE(ctx->HasInput(framework::GradVarName("Y")),
68+
"Input(Y@GRAD) should be not null.");
69+
PADDLE_ENFORCE(ctx->HasOutput(framework::GradVarName("X")),
70+
"Output(X@GRAD) should be not null.");
71+
72+
auto x_dims = ctx->GetInputDim("X");
73+
auto cvm_dims = ctx->GetInputDim("CVM");
74+
auto dy_dims = ctx->GetInputDim(framework::GradVarName("Y"));
75+
PADDLE_ENFORCE_EQ(x_dims.size(), 2, "Input(X)'s rank should be 2.");
76+
PADDLE_ENFORCE_EQ(dy_dims.size(), 2, "Input(Y@Grad)'s rank should be 2.");
77+
PADDLE_ENFORCE_EQ(cvm_dims.size(), 2, "Input(CVM)'s rank should be 2.");
78+
79+
PADDLE_ENFORCE_EQ(x_dims[0], dy_dims[0],
80+
"The 1st dimension of Input(X) and Input(Y@Grad) should "
81+
"be equal.");
82+
83+
PADDLE_ENFORCE_EQ(cvm_dims[1], 2,
84+
"When Attr(soft_label) == false, the 2nd dimension of "
85+
"Input(CVM) should be 2.");
86+
ctx->SetOutputDim(framework::GradVarName("X"), x_dims);
87+
ctx->ShareLoD("X", framework::GradVarName("X"));
88+
}
89+
90+
protected:
91+
// Explicitly set that the data type of computation kernel of
92+
// cvm
93+
// is determined by its input "X".
94+
framework::OpKernelType GetExpectedKernelType(
95+
const framework::ExecutionContext& ctx) const override {
96+
return framework::OpKernelType(ctx.Input<Tensor>("X")->type(),
97+
platform::CPUPlace());
98+
}
99+
};
100+
101+
class CVMOpMaker : public framework::OpProtoAndCheckerMaker {
102+
public:
103+
void Make() override {
104+
AddInput("X",
105+
"(LodTensor, default LodTensor<float>), a 2-D tensor with shape "
106+
"[N x D],"
107+
" where N is the batch size and D is the emebdding dim. ");
108+
AddInput("CVM",
109+
"(Tensor), a 2-D Tensor with shape [N x 2], where N is the batch "
110+
"size, 2 is show and click.");
111+
AddOutput("Y",
112+
"(LodTensor, default LodTensor<float>), a 2-D tensor with shape "
113+
"[N x K].");
114+
AddAttr<bool>("use_cvm", "bool, use cvm or not").SetDefault(true);
115+
AddComment(R"DOC(
116+
CVM Operator.
117+
118+
We assume that input X is a embedding vector with cvm_feature(show and click), which shape is [N * D] (D is 2(cvm_feature) + embedding dim, N is batch_size)
119+
if use_cvm is True, we will log(cvm_feature), and output shape is [N * D].
120+
if use_cvm is False, we will remove cvm_feature from input, and output shape is [N * (D - 2)].
121+
122+
)DOC");
123+
}
124+
};
125+
126+
class CVMGradOpDescMaker : public framework::SingleGradOpDescMaker {
127+
public:
128+
using framework::SingleGradOpDescMaker::SingleGradOpDescMaker;
129+
130+
protected:
131+
std::unique_ptr<framework::OpDesc> Apply() const override {
132+
std::unique_ptr<framework::OpDesc> op(new framework::OpDesc());
133+
op->SetType("cvm_grad");
134+
op->SetInput("X", Input("X"));
135+
op->SetInput("CVM", Input("CVM"));
136+
op->SetInput(framework::GradVarName("Y"), OutputGrad("Y"));
137+
op->SetOutput(framework::GradVarName("X"), InputGrad("X"));
138+
op->SetAttrMap(Attrs());
139+
return op;
140+
}
141+
};
142+
143+
} // namespace operators
144+
} // namespace paddle
145+
146+
namespace ops = paddle::operators;
147+
REGISTER_OPERATOR(cvm, ops::CVMOp, ops::CVMOpMaker, ops::CVMGradOpDescMaker);
148+
149+
REGISTER_OPERATOR(cvm_grad, ops::CVMGradientOp);
150+
151+
REGISTER_OP_CPU_KERNEL(cvm, ops::CVMOpKernel<float>, ops::CVMOpKernel<double>);
152+
153+
REGISTER_OP_CPU_KERNEL(cvm_grad, ops::CVMGradOpKernel<float>,
154+
ops::CVMGradOpKernel<double>);

paddle/fluid/operators/cvm_op.h

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/* Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
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+
#pragma once
16+
#include "paddle/fluid/framework/eigen.h"
17+
#include "paddle/fluid/framework/op_registry.h"
18+
19+
namespace paddle {
20+
namespace operators {
21+
22+
using Tensor = framework::Tensor;
23+
using LoDTensor = framework::LoDTensor;
24+
25+
template <typename T>
26+
class CVMOpKernel : public framework::OpKernel<T> {
27+
public:
28+
void Compute(const framework::ExecutionContext& context) const override {
29+
const LoDTensor* x = context.Input<LoDTensor>("X");
30+
const T* x_data = x->data<T>();
31+
auto lod = x->lod()[0];
32+
int64_t item_size = x->numel() / x->dims()[0];
33+
int offset = 2;
34+
if (!context.Attr<bool>("use_cvm")) {
35+
item_size -= offset;
36+
}
37+
LoDTensor* y = context.Output<LoDTensor>("Y");
38+
T* y_data = y->mutable_data<T>(context.GetPlace());
39+
40+
int seq_num = static_cast<int>(lod.size()) - 1;
41+
for (int i = 0; i < seq_num; ++i) {
42+
int64_t seq_len = static_cast<int64_t>(lod[i + 1] - lod[i]);
43+
44+
for (int j = 0; j < seq_len; ++j) {
45+
if (context.Attr<bool>("use_cvm")) {
46+
std::memcpy(y_data, x_data, item_size * sizeof(T));
47+
y_data[0] = log(y_data[0] + 1);
48+
y_data[1] = log(y_data[1] + 1) - y_data[0];
49+
x_data += item_size;
50+
y_data += item_size;
51+
} else {
52+
std::memcpy(y_data, x_data + offset, item_size * sizeof(T));
53+
x_data += item_size + offset;
54+
y_data += item_size;
55+
}
56+
}
57+
}
58+
}
59+
};
60+
61+
template <typename T>
62+
class CVMGradOpKernel : public framework::OpKernel<T> {
63+
public:
64+
void Compute(const framework::ExecutionContext& context) const override {
65+
LoDTensor* dx = context.Output<LoDTensor>(framework::GradVarName("X"));
66+
T* dx_data = dx->mutable_data<T>(context.GetPlace());
67+
68+
const Tensor* cvm = context.Input<Tensor>("CVM");
69+
const T* cvm_data = cvm->data<T>();
70+
int offset = 2;
71+
const framework::LoDTensor* dOut =
72+
context.Input<framework::LoDTensor>(framework::GradVarName("Y"));
73+
const T* dout_data = dOut->data<T>();
74+
75+
auto lod = dx->lod()[0];
76+
int64_t item_size = dx->numel() / dx->dims()[0];
77+
if (!context.Attr<bool>("use_cvm")) {
78+
item_size -= offset;
79+
}
80+
81+
int seq_num = static_cast<int>(lod.size()) - 1;
82+
for (int i = 0; i < seq_num; ++i) {
83+
int64_t seq_len = static_cast<int64_t>(lod[i + 1] - lod[i]);
84+
85+
for (int j = 0; j < seq_len; ++j) {
86+
if (context.Attr<bool>("use_cvm")) {
87+
std::memcpy(dx_data, dout_data, item_size * sizeof(T));
88+
dx_data[0] = cvm_data[0];
89+
dx_data[1] = cvm_data[1];
90+
dx_data += item_size;
91+
dout_data += item_size;
92+
} else {
93+
std::memcpy(dx_data + offset, dout_data, item_size * sizeof(T));
94+
dx_data[0] = cvm_data[0];
95+
dx_data[1] = cvm_data[1];
96+
dx_data += item_size + offset;
97+
dout_data += item_size;
98+
}
99+
}
100+
cvm_data += offset;
101+
}
102+
}
103+
};
104+
} // namespace operators
105+
} // namespace paddle

python/paddle/fluid/layers/nn.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@
193193
'npair_loss',
194194
'pixel_shuffle',
195195
'fsp_matrix',
196+
'continuous_value_model',
196197
]
197198

198199
kIgnoreIndex = -100
@@ -11037,3 +11038,54 @@ def fsp_matrix(x, y):
1103711038
input_param_name='x'))
1103811039
helper.append_op(type='fsp', inputs={'X': x, 'Y': y}, outputs={'Out': out})
1103911040
return out
11041+
11042+
11043+
def continuous_value_model(input, cvm, use_cvm=True):
11044+
"""
11045+
11046+
**continuous_value_model layers**
11047+
11048+
continuous value model(cvm). Now, it only considers show and click value in CTR project.
11049+
We assume that input is an embedding vector with cvm_feature, whose shape is [N * D] (D is 2 + embedding dim).
11050+
If use_cvm is True, it will log(cvm_feature), and output shape is [N * D].
11051+
If use_cvm is False, it will remove cvm_feature from input, and output shape is [N * (D - 2)].
11052+
11053+
This layer accepts a tensor named input which is ID after embedded(lod level is 1), cvm is a show_click info.
11054+
11055+
Args:
11056+
11057+
input (Variable): a 2-D LodTensor with shape [N x D], where N is the batch size, D is 2 + the embedding dim. lod level = 1.
11058+
cvm (Variable): a 2-D Tensor with shape [N x 2], where N is the batch size, 2 is show and click.
11059+
use_cvm (bool): use cvm or not. if use cvm, the output dim is the same as input
11060+
if don't use cvm, the output dim is input dim - 2(remove show and click)
11061+
(cvm op is a customized op, which input is a sequence has embedd_with_cvm default, so we need an op named cvm to decided whever use it or not.)
11062+
11063+
Returns:
11064+
11065+
Variable: A 2-D LodTensor with shape [N x D], if use cvm, D is equal to input dim, if don't use cvm, D is equal to input dim - 2.
11066+
11067+
Examples:
11068+
11069+
.. code-block:: python
11070+
11071+
input = fluid.layers.data(name="input", shape=[-1, 1], lod_level=1, append_batch_size=False, dtype="int64")#, stop_gradient=False)
11072+
label = fluid.layers.data(name="label", shape=[-1, 1], append_batch_size=False, dtype="int64")
11073+
embed = fluid.layers.embedding(
11074+
input=input,
11075+
size=[100, 11],
11076+
dtype='float32')
11077+
ones = fluid.layers.fill_constant_batch_size_like(input=label, shape=[-1, 1], dtype="int64", value=1)
11078+
show_clk = fluid.layers.cast(fluid.layers.concat([ones, label], axis=1), dtype='float32')
11079+
show_clk.stop_gradient = True
11080+
input_with_cvm = fluid.layers.continuous_value_model(embed, show_clk, True)
11081+
11082+
"""
11083+
helper = LayerHelper('cvm', **locals())
11084+
out = helper.create_variable(dtype=input.dtype)
11085+
helper.append_op(
11086+
type='cvm',
11087+
inputs={'X': [input],
11088+
'CVM': [cvm]},
11089+
outputs={'Y': [out]},
11090+
attrs={"use_cvm": use_cvm})
11091+
return out
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
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 numpy as np
16+
from math import log
17+
from math import exp
18+
from op_test import OpTest
19+
import unittest
20+
21+
22+
class TestCVMOp(OpTest):
23+
"""
24+
Test cvm op with discrete one-hot labels.
25+
"""
26+
27+
def setUp(self):
28+
self.op_type = "cvm"
29+
batch_size = 4
30+
dims = 11
31+
lod = [[1]]
32+
self.inputs = {
33+
'X': (np.random.uniform(0, 1, [1, dims]).astype("float32"), lod),
34+
'CVM': np.array([[0.6, 0.4]]).astype("float32"),
35+
}
36+
self.attrs = {'use_cvm': False}
37+
out = []
38+
for index, emb in enumerate(self.inputs["X"][0]):
39+
out.append(emb[2:])
40+
self.outputs = {'Y': (np.array(out), lod)}
41+
42+
def test_check_output(self):
43+
self.check_output()
44+
45+
46+
if __name__ == '__main__':
47+
unittest.main()

0 commit comments

Comments
 (0)