Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
PaddlePaddle
Paddle
提交
db157eda
P
Paddle
项目概览
PaddlePaddle
/
Paddle
大约 1 年 前同步成功
通知
2298
Star
20931
Fork
5422
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
1423
列表
看板
标记
里程碑
合并请求
543
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
P
Paddle
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
1,423
Issue
1,423
列表
看板
标记
里程碑
合并请求
543
合并请求
543
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
提交
db157eda
编写于
10月 23, 2017
作者:
Y
Yang Yang(Tony)
提交者:
GitHub
10月 23, 2017
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
New Op Test framework. (#4962)
Pass all forward op test
上级
bc151174
变更
17
隐藏空白更改
内联
并排
Showing
17 changed file
with
157 addition
and
638 deletion
+157
-638
paddle/operators/crop_op.cc
paddle/operators/crop_op.cc
+2
-1
paddle/operators/fc_op.cc
paddle/operators/fc_op.cc
+0
-200
paddle/operators/gru_unit_op.cc
paddle/operators/gru_unit_op.cc
+3
-3
paddle/operators/identity_op.cc
paddle/operators/identity_op.cc
+0
-63
paddle/operators/interp_op.cc
paddle/operators/interp_op.cc
+0
-113
paddle/operators/reduce_op.cc
paddle/operators/reduce_op.cc
+0
-62
paddle/operators/smooth_l1_loss_op.cc
paddle/operators/smooth_l1_loss_op.cc
+4
-2
python/paddle/v2/framework/framework.py
python/paddle/v2/framework/framework.py
+28
-27
python/paddle/v2/framework/tests/op_test.py
python/paddle/v2/framework/tests/op_test.py
+108
-19
python/paddle/v2/framework/tests/test_accuracy_op.py
python/paddle/v2/framework/tests/test_accuracy_op.py
+3
-1
python/paddle/v2/framework/tests/test_activation_op.py
python/paddle/v2/framework/tests/test_activation_op.py
+4
-4
python/paddle/v2/framework/tests/test_clip_op.py
python/paddle/v2/framework/tests/test_clip_op.py
+3
-3
python/paddle/v2/framework/tests/test_fc_op.py
python/paddle/v2/framework/tests/test_fc_op.py
+0
-62
python/paddle/v2/framework/tests/test_identity_op.py
python/paddle/v2/framework/tests/test_identity_op.py
+0
-20
python/paddle/v2/framework/tests/test_interp_op.py
python/paddle/v2/framework/tests/test_interp_op.py
+0
-28
python/paddle/v2/framework/tests/test_pad_op.py
python/paddle/v2/framework/tests/test_pad_op.py
+2
-2
python/paddle/v2/framework/tests/test_reduce_op.py
python/paddle/v2/framework/tests/test_reduce_op.py
+0
-28
未找到文件。
paddle/operators/crop_op.cc
浏览文件 @
db157eda
...
...
@@ -59,7 +59,8 @@ class CropOpMaker : public framework::OpProtoAndCheckerMaker {
"The input should be a k-D tensor(k > 0 and k < 7)"
);
AddInput
(
"Y"
,
"The input used as reference for cropping"
" with the same dimension as X. "
);
" with the same dimension as X. "
)
.
AsDispensable
();
AddOutput
(
"Out"
,
"The output of crop op "
"with the same dimension as X."
);
...
...
paddle/operators/fc_op.cc
已删除
100644 → 0
浏览文件 @
bc151174
/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve.
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. */
#include "paddle/framework/op_registry.h"
#include "paddle/operators/net_op.h"
namespace
paddle
{
namespace
operators
{
class
FCOp
:
public
NetOp
{
public:
FCOp
(
const
std
::
string
&
type
,
const
framework
::
VariableNameMap
&
inputs
,
const
framework
::
VariableNameMap
&
outputs
,
const
framework
::
AttributeMap
&
attrs
)
:
NetOp
(
type
,
inputs
,
outputs
,
attrs
)
{
PADDLE_ENFORCE
(
!
Inputs
(
"X"
).
empty
(),
"Inputs(X) of FCOp should not be null."
);
PADDLE_ENFORCE
(
!
Inputs
(
"W"
).
empty
(),
"Inputs(W) of FCOp should not be null."
);
PADDLE_ENFORCE
(
!
Outputs
(
"MulOut"
).
empty
(),
"Outputs(MulOut) of FCOp should not be null."
);
PADDLE_ENFORCE_NE
(
Output
(
"Out"
),
framework
::
kEmptyVarName
,
"Output(Out) of FCOp should not be null."
);
auto
x
=
Inputs
(
"X"
);
auto
w
=
Inputs
(
"W"
);
auto
mul_out
=
Outputs
(
"MulOut"
);
PADDLE_ENFORCE_EQ
(
x
.
size
(),
w
.
size
(),
"The size of inputs X(%d) should be the same as that of weights W(%d)."
,
x
.
size
(),
w
.
size
());
PADDLE_ENFORCE_EQ
(
mul_out
.
size
(),
x
.
size
(),
"The size of intermediate mul_out(%d) should be the same "
"as that of inputs X(%d)."
,
mul_out
.
size
(),
x
.
size
());
size_t
n
=
x
.
size
();
PADDLE_ENFORCE_GE
(
n
,
static_cast
<
size_t
>
(
1
),
"The size of inputs X(%d) should be no less than 1."
,
n
);
auto
x_num_col_dims
=
Attr
<
std
::
vector
<
int
>>
(
"xNumColDims"
);
// Set all values or set no values (use the default value)
if
(
!
x_num_col_dims
.
empty
())
{
PADDLE_ENFORCE_EQ
(
x_num_col_dims
.
size
(),
n
,
"The size of attribute xNumColDims(%d) should be the "
"same as that of inputs X(%d)."
,
x_num_col_dims
.
size
(),
n
);
}
else
{
x_num_col_dims
.
resize
(
n
);
for
(
size_t
i
=
0
;
i
<
n
;
i
++
)
{
x_num_col_dims
[
i
]
=
1
;
}
}
// mul_out[i] = X[i] * W[i]
for
(
size_t
i
=
0
;
i
<
n
;
i
++
)
{
framework
::
AttributeMap
mul_attr
;
mul_attr
[
"x_num_col_dims"
]
=
static_cast
<
int
>
(
x_num_col_dims
[
i
]);
mul_attr
[
"y_num_col_dims"
]
=
static_cast
<
int
>
(
1
);
AppendOp
(
framework
::
OpRegistry
::
CreateOp
(
"mul"
,
{{
"X"
,
{
x
[
i
]}},
{
"Y"
,
{
w
[
i
]}}},
{{
"Out"
,
{
mul_out
[
i
]}}},
mul_attr
));
}
// sum_out = X[0] * W[0] + ... + X[n-1] * W[n-1]
auto
sum_out
=
mul_out
[
0
];
if
(
n
>
1
)
{
PADDLE_ENFORCE_NE
(
Output
(
"SumOut"
),
framework
::
kEmptyVarName
,
"Output(SumOut) of FCOp should not be null when the "
"size of Inputs(X) > 1."
);
sum_out
=
Output
(
"SumOut"
);
AppendOp
(
framework
::
OpRegistry
::
CreateOp
(
"sum"
,
{{
"X"
,
{
mul_out
}}},
{{
"Out"
,
{
sum_out
}}},
{}));
}
else
{
if
(
Output
(
"SumOut"
)
!=
framework
::
kEmptyVarName
)
{
this
->
Rename
(
Output
(
"SumOut"
),
framework
::
kEmptyVarName
);
}
}
// add_out = sum_out + b
auto
b
=
Input
(
"B"
);
auto
add_out
=
sum_out
;
if
(
b
!=
framework
::
kEmptyVarName
)
{
PADDLE_ENFORCE_NE
(
Output
(
"AddOut"
),
framework
::
kEmptyVarName
,
"Output(AddOut) of FCOp should not be null when Input(B) is set."
);
add_out
=
Output
(
"AddOut"
);
AppendOp
(
framework
::
OpRegistry
::
CreateOp
(
"elementwise_add"
,
{{
"X"
,
{
sum_out
}},
{
"Y"
,
{
Input
(
"B"
)}}},
{{
"Out"
,
{
add_out
}}},
{}));
}
else
{
if
(
Output
(
"AddOut"
)
!=
framework
::
kEmptyVarName
)
{
this
->
Rename
(
Output
(
"AddOut"
),
framework
::
kEmptyVarName
);
}
}
auto
activation
=
Attr
<
std
::
string
>
(
"activation"
);
AppendOp
(
framework
::
OpRegistry
::
CreateOp
(
activation
,
{{
"X"
,
{
add_out
}}},
{{
"Y"
,
{
Output
(
"Out"
)}}},
{}));
CompleteAddOp
(
false
);
}
};
class
FCOpMaker
:
public
framework
::
OpProtoAndCheckerMaker
{
public:
FCOpMaker
(
framework
::
OpProto
*
proto
,
framework
::
OpAttrChecker
*
op_checker
)
:
OpProtoAndCheckerMaker
(
proto
,
op_checker
)
{
AddInput
(
"X"
,
"(A vector of Tensors) each input Tensor can be of arbitrary "
"dimension, and will be reshaped to a 2-D matrix of size "
"(minibatch, number_of_input_features) according to attribute "
"xNumColDims."
)
.
AsDuplicable
();
AddInput
(
"W"
,
"(A vector of Tensors) the weights of FC operator, a "
"vector of 2-D matrix of size "
"(number_of_input_features, number_of_neurons)."
)
.
AsDuplicable
();
AddInput
(
"B"
,
"(Tensor) the bias of FC operator, a 1-D vector of size "
"number_of_neurons."
);
AddOutput
(
"Out"
,
"(Tensor) the activated output matrix of FC operator, a 2-D "
"matrix of size (minibatch, number_of_neurons)."
);
AddOutput
(
"MulOut"
,
"(A vector of Tensors) the intermediate outputs of FC operator, "
"each Tensor saving the product of X_i * W_i."
)
.
AsIntermediate
()
.
AsDuplicable
();
AddOutput
(
"SumOut"
,
"(Tensor) the intermediate output of FC operator, "
"saving the sum of the products of X and W, that is sum{X_i * W_i}."
)
.
AsIntermediate
();
AddOutput
(
"AddOut"
,
"(Tensor) the non-actived output of FC operator, "
"saving sum{X_i * W_i} + B."
)
.
AsIntermediate
();
AddAttr
<
std
::
string
>
(
"activation"
,
"(string, default identity) the activation type of FC operator."
)
.
SetDefault
(
"identity"
)
.
InEnum
({
"identity"
,
"sigmoid"
,
"softmax"
});
AddAttr
<
std
::
vector
<
int
>>
(
"xNumColDims"
,
"(std::vector<int>) The inputs Tensors of FC operator can be of "
"more than 2 dimensions. In that case, each input Tensor `X_i` will be "
"reshaped to a 2-D matrix. The matrix's first dimension "
"(the length of column) will be the product of `X_i`'s last "
"`xNumColDims_i` dimensions, that is "
"`X_i.dims[0] x ... x X_i.dims[xNumColDims_i - 1]`. "
"The matrix's second dimension (the length of row) will be the product "
"of `X_i`'s first `rank - xNumColDims_i` dimensions, that is "
"`X_i.dims[xNumColDims_i] x ... x X_i.dims[rank - 1]`)"
)
.
SetDefault
(
std
::
vector
<
int
>
{});
AddComment
(
R"DOC(
Fully Connected Operator, known as Fully Connected Layer or Inner Product Layer
in Convolutional Neural Networks. Neurons in a fully connected layer have
full connections to all activations in the previous layer.
It computes an inner product of a set of
learned weights with a matrix multiplication followed by a bias offset
(optionally).
Equation:
Out = Act(sum_n{X_i * W_i} + B)
where X_i is Tensor that will be reshaped to a 2-D matrix of size (M x K),
usually M is the minibatch size and K is the number of input features.
W_i is a 2-D matrix of size (K x N), where N means the number of neurons
in the fully connected layer. B is a 1-D vector of size N.
Thus, the output Out is a 2-D matrix of size (M x N).
Activation type can be set to `identity` (default), `sigmoid` or `softmax`.
All the inputs can carry the LoD (Level of Details) information,
or not. But the output only shares the LoD with first input (`X[0]`).
)DOC"
);
}
};
}
// namespace operators
}
// namespace paddle
namespace
ops
=
paddle
::
operators
;
REGISTER_OP_WITHOUT_GRADIENT
(
fc
,
ops
::
FCOp
,
ops
::
FCOpMaker
);
paddle/operators/gru_unit_op.cc
浏览文件 @
db157eda
...
...
@@ -54,8 +54,7 @@ class GRUUnitOp : public framework::OperatorWithKernel {
PADDLE_ENFORCE_EQ
(
weight_width
,
frame_size
*
3
,
"The shape of Weight matrix must be [frame_size, frame_size * 3]."
);
auto
bias
=
Input
(
"Bias"
);
if
(
bias
!=
framework
::
kEmptyVarName
)
{
if
(
ctx
->
HasInput
(
"Bias"
))
{
auto
bias_dims
=
ctx
->
GetInputDim
(
"Bias"
);
int
bias_height
=
bias_dims
[
0
];
int
bias_width
=
bias_dims
[
1
];
...
...
@@ -89,7 +88,8 @@ class GRUUnitOpMaker : public framework::OpProtoAndCheckerMaker {
"weights of output candidate with shape [frame_size, frame_size]"
);
AddInput
(
"Bias"
,
"(Tensor) Bias vector with shape [1, frame_size * 3] concating "
"bias of the update gate, reset gate and output candidate."
);
"bias of the update gate, reset gate and output candidate."
)
.
AsDispensable
();
AddOutput
(
"Gate"
,
"(Tensor) Matrix with shape [batch_size, frame_size * 3] for the "
"output of update gate, reset gate and output candidate"
)
...
...
paddle/operators/identity_op.cc
已删除
100644 → 0
浏览文件 @
bc151174
/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve.
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. */
#include "paddle/operators/net_op.h"
#include "paddle/operators/scale_op.h"
namespace
paddle
{
namespace
operators
{
// The identity operator is an alias of the scale operator. This is also an
// example for creating an alias for an existing operator.
template
<
typename
AttrType
>
class
IdentityOpMaker
:
public
framework
::
OpProtoAndCheckerMaker
{
public:
IdentityOpMaker
(
framework
::
OpProto
*
proto
,
framework
::
OpAttrChecker
*
op_checker
)
:
OpProtoAndCheckerMaker
(
proto
,
op_checker
)
{
AddInput
(
"X"
,
"The input tensor of identity operator."
);
AddOutput
(
"Y"
,
"The output tensor of identity operator."
);
AddComment
(
R"DOC(
The identity operator is an alias of the scale operator
with the attribute scale fixed to 1.0.
)DOC"
);
}
};
template
<
typename
AttrType
>
class
IdentityOp
:
public
NetOp
{
public:
IdentityOp
(
const
std
::
string
&
type
,
const
framework
::
VariableNameMap
&
inputs
,
const
framework
::
VariableNameMap
&
outputs
,
const
framework
::
AttributeMap
&
attrs
)
:
NetOp
(
type
,
inputs
,
outputs
,
attrs
)
{
PADDLE_ENFORCE_NE
(
Input
(
"X"
),
framework
::
kEmptyVarName
,
"Input(X) of IdentityOp should not be null."
);
PADDLE_ENFORCE_NE
(
Output
(
"Y"
),
framework
::
kEmptyVarName
,
"Output(Y) of IdentityOp should not be null."
);
AppendOp
(
framework
::
OpRegistry
::
CreateOp
(
"scale"
,
{{
"X"
,
{
Input
(
"X"
)}}},
{{
"Out"
,
{
Output
(
"Y"
)}}},
{{
"scale"
,
static_cast
<
AttrType
>
(
1
)}}));
CompleteAddOp
(
false
);
}
};
}
// namespace operators
}
// namespace paddle
namespace
ops
=
paddle
::
operators
;
REGISTER_OP_WITHOUT_GRADIENT
(
identity
,
ops
::
IdentityOp
<
float
>
,
ops
::
IdentityOpMaker
<
float
>
);
paddle/operators/interp_op.cc
已删除
100644 → 0
浏览文件 @
bc151174
/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve.
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. */
#include "paddle/framework/op_registry.h"
#include "paddle/operators/net_op.h"
namespace
paddle
{
namespace
operators
{
class
InterpOp
:
public
NetOp
{
public:
InterpOp
(
const
std
::
string
&
type
,
const
framework
::
VariableNameMap
&
inputs
,
const
framework
::
VariableNameMap
&
outputs
,
const
framework
::
AttributeMap
&
attrs
)
:
NetOp
(
type
,
inputs
,
outputs
,
attrs
)
{
PADDLE_ENFORCE_NE
(
Input
(
"X"
),
framework
::
kEmptyVarName
,
"Input(X) of InterpOp should not be null."
);
PADDLE_ENFORCE_NE
(
Input
(
"Y"
),
framework
::
kEmptyVarName
,
"Input(Y) of InterpOp should not be null."
);
PADDLE_ENFORCE_NE
(
Input
(
"W"
),
framework
::
kEmptyVarName
,
"Input(W) of InterpOp should not be null."
);
PADDLE_ENFORCE_NE
(
Output
(
"SubOut"
),
framework
::
kEmptyVarName
,
"Output(SubOut) of InterpOp should not be null."
);
PADDLE_ENFORCE_NE
(
Output
(
"MulOut"
),
framework
::
kEmptyVarName
,
"Output(MulOut) of InterpOp should not be null."
);
PADDLE_ENFORCE_NE
(
Output
(
"Out"
),
framework
::
kEmptyVarName
,
"Output(Out) of InterpOp should not be null."
);
// SubOut = X - Y
auto
x
=
Input
(
"X"
);
auto
y
=
Input
(
"Y"
);
auto
sub_out
=
Output
(
"SubOut"
);
AppendOp
(
framework
::
OpRegistry
::
CreateOp
(
"elementwise_sub"
,
{{
"X"
,
{
x
}},
{
"Y"
,
{
y
}}},
{{
"Out"
,
{
sub_out
}}},
{}));
// MulOut = SubOut * W = (X - Y) * W
auto
w
=
Input
(
"W"
);
auto
mul_out
=
Output
(
"MulOut"
);
AppendOp
(
framework
::
OpRegistry
::
CreateOp
(
"elementwise_mul"
,
{{
"X"
,
{
sub_out
}},
{
"Y"
,
{
w
}}},
{{
"Out"
,
{
mul_out
}}},
{{
"axis"
,
0
}}));
// Out = MulOut + Y = (X - Y) * W + Y = X * W + Y * (1 - W)
AppendOp
(
framework
::
OpRegistry
::
CreateOp
(
"elementwise_add"
,
{{
"X"
,
{
mul_out
}},
{
"Y"
,
{
y
}}},
{{
"Out"
,
{
Output
(
"Out"
)}}},
{}));
CompleteAddOp
(
false
);
}
};
class
InterpOpMaker
:
public
framework
::
OpProtoAndCheckerMaker
{
public:
InterpOpMaker
(
framework
::
OpProto
*
proto
,
framework
::
OpAttrChecker
*
op_checker
)
:
OpProtoAndCheckerMaker
(
proto
,
op_checker
)
{
AddInput
(
"X"
,
"(Tensor), 2-D Matrix of shape [batch_size, data_dim]"
"containing data samples, the first input of interp_op"
);
AddInput
(
"Y"
,
"(Tensor), 2-D Matrix of shape `[batch_size, data_dim]`"
"containing data samples, the second input of interp_op"
);
AddInput
(
"W"
,
"(Tensor), 1-D Vector of shape [batch_size],"
"the interpolated values in the half-open interval [0.0, 1.0)"
);
AddOutput
(
"SubOut"
,
"(Tensor), the intermediate subtraction outputs, saving X - Y."
)
.
AsIntermediate
();
AddOutput
(
"MulOut"
,
"(Tensor), the intermediate multiplication outputs,"
"saving the elementwise multiplication of (X - Y) and W."
)
.
AsIntermediate
();
AddOutput
(
"Out"
,
"(Tensor), the output of interp_op, same shape with X,"
"returns the first-dimensional piecewise linear interpolant "
"between X and Y"
);
AddComment
(
R"DOC(
Linear Interpolation with two inputs, used in NEURAL TURING MACHINE.
Equation:
Out.row[i] = X.row[i] * W[i] + Y.row[i] * (1 - W[i])
= (X.row[i] - Y.row[i]) * W[i] + Y.row[i]
Example:
X = [[1,2],[3,4]],
Y = [[2,1],[4,3]],
W = [0.3, 0.4]
Then, Out = [[1.7,1.3],[3.6,3.4]]
where 1.7 = 1*0.3+2*(1-0.3),
1.3 = 2*0.3+1*(1-0.3),
3.6 = 3*0.4+4*(1-0.4),
3.4 = 4*0.4+3*(1-0.4)
)DOC"
);
}
};
}
// namespace operators
}
// namespace paddle
namespace
ops
=
paddle
::
operators
;
REGISTER_OP_WITHOUT_GRADIENT
(
interp
,
ops
::
InterpOp
,
ops
::
InterpOpMaker
);
paddle/operators/reduce_op.cc
浏览文件 @
db157eda
...
...
@@ -160,66 +160,6 @@ class ReduceMinOpMaker : public ReduceOpMaker {
}
};
class
NormOp
:
public
NetOp
{
public:
NormOp
(
const
std
::
string
&
type
,
const
framework
::
VariableNameMap
&
inputs
,
const
framework
::
VariableNameMap
&
outputs
,
const
framework
::
AttributeMap
&
attrs
)
:
NetOp
(
type
,
inputs
,
outputs
,
attrs
)
{
PADDLE_ENFORCE_NE
(
Input
(
"X"
),
framework
::
kEmptyVarName
,
"Input(X) of NormOp should not be null."
);
PADDLE_ENFORCE_NE
(
Output
(
"AbsOut"
),
framework
::
kEmptyVarName
,
"Output(AbsOut) of NormOp should not be null."
);
PADDLE_ENFORCE_NE
(
Output
(
"PowOut"
),
framework
::
kEmptyVarName
,
"Output(PowOut) of NormOp should not be null."
);
PADDLE_ENFORCE_NE
(
Output
(
"SumOut"
),
framework
::
kEmptyVarName
,
"Output(SumOut) of NormOp should not be null."
);
PADDLE_ENFORCE_NE
(
Output
(
"Out"
),
framework
::
kEmptyVarName
,
"Output(Out) of NormOp should not be null."
);
auto
dim
=
Attr
<
int
>
(
"dim"
);
auto
keep_dim
=
Attr
<
bool
>
(
"keep_dim"
);
auto
p
=
Attr
<
float
>
(
"p"
);
PADDLE_ENFORCE_GT
(
p
,
0
,
"Order of the norm should be positive."
);
AppendOp
(
framework
::
OpRegistry
::
CreateOp
(
"abs"
,
{{
"X"
,
{
Input
(
"X"
)}}},
{{
"Y"
,
{
Output
(
"AbsOut"
)}}},
{}));
AppendOp
(
framework
::
OpRegistry
::
CreateOp
(
"pow"
,
{{
"X"
,
{
Output
(
"AbsOut"
)}}},
{{
"Y"
,
{
Output
(
"PowOut"
)}}},
{{
"factor"
,
p
}}));
framework
::
AttributeMap
sum_attr
;
sum_attr
[
"dim"
]
=
dim
;
sum_attr
[
"keep_dim"
]
=
keep_dim
;
AppendOp
(
framework
::
OpRegistry
::
CreateOp
(
"reduce_sum"
,
{{
"X"
,
{
Output
(
"PowOut"
)}}},
{{
"Out"
,
{
Output
(
"SumOut"
)}}},
sum_attr
));
AppendOp
(
framework
::
OpRegistry
::
CreateOp
(
"pow"
,
{{
"X"
,
{
Output
(
"SumOut"
)}}},
{{
"Y"
,
{
Output
(
"Out"
)}}},
{{
"factor"
,
static_cast
<
float
>
(
1.
/
p
)}}));
CompleteAddOp
(
false
);
}
};
class
NormOpMaker
:
public
ReduceOpMaker
{
public:
NormOpMaker
(
framework
::
OpProto
*
proto
,
framework
::
OpAttrChecker
*
op_checker
)
:
ReduceOpMaker
(
proto
,
op_checker
)
{
AddOutput
(
"AbsOut"
,
"(Tensor) The intermediate output of Norm operator, "
"saving the absolute value of the input tensor X."
)
.
AsIntermediate
();
AddOutput
(
"PowOut"
,
"(Tensor) The intermediate output of Norm operator, "
"saving the p-th power of the output tensor AbsOut."
)
.
AsIntermediate
();
AddOutput
(
"SumOut"
,
"(Tensor) the intermediate output of Norm operator, "
"saving the sum of PowOut reduced on the given dimension."
)
.
AsIntermediate
();
AddAttr
<
float
>
(
"p"
,
"(float, default 2) The order of Norm."
).
SetDefault
(
2
);
SetComment
(
"Norm"
,
"vector p-norm"
);
AddComment
(
comment_
);
}
};
}
// namespace operators
}
// namespace paddle
...
...
@@ -237,8 +177,6 @@ REGISTER_OP(reduce_max, ops::ReduceOp, ops::ReduceMaxOpMaker, reduce_max_grad,
REGISTER_OP
(
reduce_min
,
ops
::
ReduceOp
,
ops
::
ReduceMinOpMaker
,
reduce_min_grad
,
ops
::
ReduceGradOp
);
REGISTER_OP_WITHOUT_GRADIENT
(
norm
,
ops
::
NormOp
,
ops
::
NormOpMaker
);
#define REGISTER_REDUCE_CPU_KERNEL(reduce_type, functor, grad_functor) \
REGISTER_OP_CPU_KERNEL( \
reduce_type, \
...
...
paddle/operators/smooth_l1_loss_op.cc
浏览文件 @
db157eda
...
...
@@ -62,11 +62,13 @@ class SmoothL1LossOpMaker : public framework::OpProtoAndCheckerMaker {
AddInput
(
"InsideWeight"
,
"Optional input tensor of smooth l1 loss op with the same shape "
"as X. If provided, the result of (X - Y) will be multiplied "
"by this tensor element by element."
);
"by this tensor element by element."
)
.
AsDispensable
();
AddInput
(
"OutsideWeight"
,
"Optinal input of smooth l1 loss op with the same shape as X."
"If provided, the output smooth l1 loss will be multiplied by "
"this tensor element by element."
);
"this tensor element by element."
)
.
AsDispensable
();
AddOutput
(
"Diff"
,
"Intermediate variable to cache InsideWeight*(X-Y)."
)
.
AsIntermediate
();
AddOutput
(
"Out"
,
"Smooth l1 loss."
);
...
...
python/paddle/v2/framework/framework.py
浏览文件 @
db157eda
...
...
@@ -191,32 +191,33 @@ class Operator(object):
"`type` to initilized an Operator can not be None."
)
self
.
desc
.
set_type
(
type
)
proto
=
OpProtoHolder
.
instance
().
get_op_proto
(
type
)
if
inputs
is
not
None
:
given
=
set
()
need
=
set
()
for
n
in
inputs
:
given
.
add
(
n
)
for
m
in
proto
.
inputs
:
need
.
add
(
m
.
name
)
if
not
given
==
need
:
raise
ValueError
(
"Incorrect setting for input(s) of operator
\"
%s
\"
. Need: [%s] Given: [%s]"
%
(
type
,
", "
.
join
(
str
(
e
)
for
e
in
need
),
", "
.
join
(
str
(
e
)
for
e
in
given
)))
for
in_proto
in
proto
.
inputs
:
def
find_name
(
var_list
,
name
):
for
var_name
in
var_list
:
if
var_name
==
name
:
return
True
return
False
in_argus
=
inputs
[
in_proto
.
name
]
if
not
isinstance
(
in_argus
,
list
):
in_argus
=
[
in_argus
]
if
not
in_proto
.
duplicable
and
len
(
in_argus
)
>
1
:
raise
ValueError
(
"Input %s expects only one input, but %d are given."
%
(
in_proto
.
name
,
len
(
in_argus
)))
in_argu_names
=
[]
for
argu
in
in_argus
:
in_argu_names
.
append
(
argu
.
name
)
self
.
desc
.
set_input
(
in_proto
.
name
,
in_argu_names
)
if
inputs
is
not
None
:
for
in_proto
in
proto
.
inputs
:
found
=
find_name
(
inputs
,
in_proto
.
name
)
assert
found
or
in_proto
.
dispensable
,
"Input {} not found"
.
format
(
in_proto
.
name
)
if
found
:
in_argus
=
inputs
[
in_proto
.
name
]
if
not
isinstance
(
in_argus
,
list
):
in_argus
=
[
in_argus
]
if
not
in_proto
.
duplicable
and
len
(
in_argus
)
>
1
:
raise
ValueError
(
"Input %s expects only one input, but %d are given."
%
(
in_proto
.
name
,
len
(
in_argus
)))
in_argu_names
=
[]
for
argu
in
in_argus
:
in_argu_names
.
append
(
argu
.
name
)
self
.
desc
.
set_input
(
in_proto
.
name
,
in_argu_names
)
else
:
self
.
desc
.
set_input
(
in_proto
.
name
,
[])
if
outputs
is
not
None
:
given
=
set
()
...
...
@@ -250,10 +251,10 @@ class Operator(object):
attr_name
=
attr
.
name
if
(
not
attr_name
in
attrs
)
or
(
attrs
[
attr_name
]
is
None
):
continue
if
not
isinstance
(
attrs
[
attr_name
],
Block
):
self
.
desc
.
set_attr
(
attr_name
,
attrs
[
attr_name
])
else
:
if
isinstance
(
attrs
[
attr_name
],
Block
):
self
.
desc
.
set_block_attr
(
attr_name
,
attrs
[
attr_name
].
desc
)
else
:
self
.
desc
.
set_attr
(
attr_name
,
attrs
[
attr_name
])
self
.
desc
.
check_attrs
()
if
type
not
in
{
'feed'
,
'fetch'
}:
...
...
python/paddle/v2/framework/tests/op_test.py
浏览文件 @
db157eda
...
...
@@ -4,6 +4,8 @@ import random
import
itertools
import
paddle.v2.framework.core
as
core
from
paddle.v2.framework.op
import
Operator
from
paddle.v2.framework.executor
import
Executor
from
paddle.v2.framework.framework
import
Program
,
OpProtoHolder
def
grad_var_name
(
var_name
):
...
...
@@ -197,6 +199,48 @@ def get_gradient(scope, op, inputs, outputs, grad_name, place,
return
out
def
append_input_output
(
block
,
op_proto
,
np_list
,
is_input
):
'''Insert VarDesc and generate Python variable instance'''
proto_list
=
op_proto
.
inputs
if
is_input
else
op_proto
.
outputs
def
create_var
(
block
,
name
,
np_list
,
var_proto
):
if
name
not
in
np_list
:
assert
var_proto
.
intermediate
,
"{} not found"
.
format
(
name
)
shape
=
None
lod_level
=
None
else
:
np_value
=
np_list
[
name
]
if
isinstance
(
np_value
,
tuple
):
shape
=
list
(
np_value
[
0
].
shape
)
lod_level
=
len
(
np_value
[
1
])
else
:
shape
=
list
(
np_value
.
shape
)
lod_level
=
0
return
block
.
create_var
(
dtype
=
"float32"
,
shape
=
shape
,
lod_level
=
lod_level
,
name
=
name
)
var_dict
=
{}
for
var_proto
in
proto_list
:
var_name
=
str
(
var_proto
.
name
)
if
is_input
:
if
(
var_name
not
in
np_list
)
and
var_proto
.
dispensable
:
continue
assert
(
var_name
in
np_list
)
or
(
var_proto
.
dispensable
),
\
"Missing {} as input"
.
format
(
var_name
)
if
var_proto
.
duplicable
:
assert
isinstance
(
np_list
[
var_name
],
list
),
\
"Duplicable {} should be set as list"
.
format
(
var_name
)
var_list
=
[]
for
(
name
,
np_value
)
in
np_list
[
var_name
]:
var_list
.
append
(
create_var
(
block
,
name
,
{
name
:
np_value
},
var_proto
))
var_dict
[
var_name
]
=
var_list
else
:
var_dict
[
var_name
]
=
create_var
(
block
,
var_name
,
np_list
,
var_proto
)
return
var_dict
class
OpTest
(
unittest
.
TestCase
):
@
classmethod
def
setUpClass
(
cls
):
...
...
@@ -213,40 +257,85 @@ class OpTest(unittest.TestCase):
np
.
random
.
set_state
(
cls
.
_np_rand_state
)
random
.
setstate
(
cls
.
_py_rand_state
)
def
feed_var
(
self
,
input_vars
,
place
):
feed_map
=
{}
for
var_name
in
input_vars
:
if
isinstance
(
input_vars
[
var_name
],
list
):
for
name
,
np_value
in
self
.
inputs
[
var_name
]:
tensor
=
core
.
LoDTensor
()
tensor
.
set
(
np_value
,
place
)
feed_map
[
name
]
=
tensor
else
:
tensor
=
core
.
LoDTensor
()
if
isinstance
(
self
.
inputs
[
var_name
],
tuple
):
tensor
.
set
(
self
.
inputs
[
var_name
][
0
],
place
)
tensor
.
set_lod
(
self
.
inputs
[
var_name
][
1
])
else
:
tensor
.
set
(
self
.
inputs
[
var_name
],
place
)
feed_map
[
var_name
]
=
tensor
return
feed_map
def
check_output_with_place
(
self
,
place
,
atol
):
self
.
scope
=
core
.
Scope
()
op_inputs
=
self
.
inputs
if
hasattr
(
self
,
"inputs"
)
else
dict
()
op_outputs
=
self
.
outputs
if
hasattr
(
self
,
"outputs"
)
else
dict
()
op_attrs
=
self
.
attrs
if
hasattr
(
self
,
"attrs"
)
else
dict
()
self
.
op
=
create_op
(
self
.
scope
,
self
.
op_type
,
op_inputs
,
op_outputs
,
op_attrs
)
if
isinstance
(
place
,
core
.
GPUPlace
)
and
not
self
.
op
.
support_gpu
():
return
set_input
(
self
.
scope
,
self
.
op
,
self
.
inputs
,
place
)
ctx
=
core
.
DeviceContext
.
create
(
place
)
self
.
op
.
run
(
self
.
scope
,
ctx
)
op_proto
=
OpProtoHolder
.
instance
().
get_op_proto
(
self
.
op_type
)
program
=
Program
()
block
=
program
.
global_block
()
inputs
=
append_input_output
(
block
,
op_proto
,
self
.
inputs
,
True
)
outputs
=
append_input_output
(
block
,
op_proto
,
self
.
outputs
,
False
)
op
=
block
.
append_op
(
type
=
self
.
op_type
,
inputs
=
inputs
,
outputs
=
outputs
,
attrs
=
self
.
attrs
if
hasattr
(
self
,
"attrs"
)
else
dict
())
fetch_list
=
[]
for
var_name
,
var
in
outputs
.
iteritems
():
if
var_name
in
self
.
outputs
:
if
isinstance
(
var
,
list
):
for
v
in
var
:
fetch_list
.
append
(
v
)
else
:
fetch_list
.
append
(
var
)
for
out_name
,
out_dup
in
Operator
.
get_op_outputs
(
self
.
op
.
type
()):
feed_map
=
self
.
feed_var
(
inputs
,
place
)
exe
=
Executor
(
place
)
outs
=
exe
.
run
(
program
,
feed
=
feed_map
,
fetch_list
=
fetch_list
)
for
out_name
,
out_dup
in
Operator
.
get_op_outputs
(
self
.
op_type
):
if
out_name
not
in
self
.
outputs
:
continue
def
find_actual
(
target_name
,
fetch_list
):
found
=
[
i
for
i
,
var
in
enumerate
(
fetch_list
)
if
var
.
name
==
target_name
]
self
.
assertTrue
(
len
(
found
)
==
1
,
"Found {} {}"
.
format
(
len
(
found
),
target_name
))
return
found
[
0
]
if
out_dup
:
sub_out
=
self
.
outputs
[
out_name
]
if
not
isinstance
(
sub_out
,
list
):
raise
AssertionError
(
"sub_out type %s is not list"
,
type
(
sub_out
))
for
sub_out_name
,
expect
in
sub_out
:
actual
=
np
.
array
(
self
.
scope
.
find_var
(
sub_out_name
).
get_tensor
())
idx
=
find_actual
(
sub_out_name
,
fetch_list
)
actual
=
outs
[
idx
]
self
.
assertTrue
(
np
.
allclose
(
actual
,
expect
,
atol
=
atol
),
"Output ("
+
out_name
+
") has diff at "
+
str
(
place
))
"Output ("
+
sub_out_name
+
") has diff at "
+
str
(
place
))
else
:
actual
=
np
.
array
(
self
.
scope
.
find_var
(
out_name
).
get_tensor
())
idx
=
find_actual
(
out_name
,
fetch_list
)
actual
=
outs
[
idx
]
expect
=
self
.
outputs
[
out_name
]
self
.
assertTrue
(
np
.
allclose
(
actual
,
expect
,
atol
=
atol
),
...
...
@@ -254,7 +343,7 @@ class OpTest(unittest.TestCase):
def
check_output
(
self
,
atol
=
1e-5
):
places
=
[
core
.
CPUPlace
()]
if
core
.
is_compile_gpu
():
if
core
.
is_compile_gpu
()
and
core
.
op_support_gpu
(
self
.
op_type
)
:
places
.
append
(
core
.
GPUPlace
(
0
))
for
place
in
places
:
self
.
check_output_with_place
(
place
,
atol
)
...
...
python/paddle/v2/framework/tests/test_accuracy_op.py
浏览文件 @
db157eda
...
...
@@ -16,7 +16,9 @@ class TestAccuracyOp(OpTest):
if
ele
==
label
[
rowid
]:
num_correct
+=
1
break
self
.
outputs
=
{
'Accuracy'
:
[
num_correct
/
float
(
n
)]}
self
.
outputs
=
{
'Accuracy'
:
np
.
array
([
num_correct
/
float
(
n
)]).
astype
(
"float32"
)
}
def
test_check_output
(
self
):
self
.
check_output
()
...
...
python/paddle/v2/framework/tests/test_activation_op.py
浏览文件 @
db157eda
...
...
@@ -172,8 +172,8 @@ class TestBRelu(OpTest):
def
setUp
(
self
):
self
.
op_type
=
"brelu"
x
=
np
.
random
.
uniform
(
-
1
,
1
,
[
4
,
4
]).
astype
(
"float32"
)
t_min
=
1
t_max
=
4
t_min
=
1
.0
t_max
=
4
.0
# The same with TestAbs
x
[
np
.
abs
(
x
-
t_min
)
<
0.005
]
=
t_min
+
0.02
x
[
np
.
abs
(
x
-
t_max
)
<
0.005
]
=
t_max
+
0.02
...
...
@@ -218,7 +218,7 @@ class TestSoftRelu(OpTest):
def
setUp
(
self
):
self
.
op_type
=
"soft_relu"
x
=
np
.
random
.
uniform
(
-
3
,
3
,
[
4
,
4
]).
astype
(
"float32"
)
threshold
=
2
threshold
=
2
.0
# The same reason with TestAbs
x
[
np
.
abs
(
x
-
threshold
)
<
0.005
]
=
threshold
+
0.02
x
[
np
.
abs
(
x
+
threshold
)
<
0.005
]
=
-
threshold
+
0.02
...
...
@@ -303,7 +303,7 @@ class TestPow(OpTest):
def
setUp
(
self
):
self
.
op_type
=
"pow"
self
.
inputs
=
{
'X'
:
np
.
random
.
uniform
(
1
,
2
,
[
11
,
17
]).
astype
(
"float32"
)}
self
.
attrs
=
{
'factor'
:
3
}
self
.
attrs
=
{
'factor'
:
3
.0
}
self
.
outputs
=
{
'Y'
:
np
.
power
(
self
.
inputs
[
'X'
],
3
)}
def
test_check_output
(
self
):
...
...
python/paddle/v2/framework/tests/test_clip_op.py
浏览文件 @
db157eda
...
...
@@ -37,14 +37,14 @@ class TestCase1(TestClipOp):
def
initTestCase
(
self
):
self
.
shape
=
(
8
,
16
,
8
)
self
.
max
=
0.7
self
.
min
=
0
self
.
min
=
0
.0
class
TestCase2
(
TestClipOp
):
def
initTestCase
(
self
):
self
.
shape
=
(
8
,
16
)
self
.
max
=
1
self
.
min
=
0
self
.
max
=
1
.0
self
.
min
=
0
.0
class
TestCase3
(
TestClipOp
):
...
...
python/paddle/v2/framework/tests/test_fc_op.py
已删除
100644 → 0
浏览文件 @
bc151174
import
unittest
import
numpy
as
np
from
op_test
import
OpTest
class
TestFCOp1
(
OpTest
):
def
setUp
(
self
):
x0
=
np
.
random
.
random
((
16
,
32
)).
astype
(
"float32"
)
w0
=
np
.
random
.
random
((
32
,
10
)).
astype
(
"float32"
)
mul_out0
=
np
.
dot
(
x0
,
w0
)
identity_out
=
mul_out0
self
.
op_type
=
"fc"
self
.
inputs
=
{
"X"
:
[(
"X0"
,
x0
)],
"W"
:
[(
"W0"
,
w0
)]}
self
.
outputs
=
{
"MulOut"
:
[(
"MulOut0"
,
mul_out0
)],
"Out"
:
identity_out
}
def
test_check_output
(
self
):
self
.
check_output
()
def
test_check_grad
(
self
):
self
.
check_grad
([
"X0"
,
"W0"
],
"Out"
,
max_relative_error
=
0.01
)
# FIXME: Disable TestFCOp2 since C++ fc will be removed
# class TestFCOp2(OpTest):
# def setUp(self):
# x0 = np.random.random((16, 4, 8)).astype("float32")
# x1 = np.random.random((4, 4, 32)).astype("float32")
# w0 = np.random.random((32, 10)).astype("float32")
# w1 = np.random.random((32, 10)).astype("float32")
# b = np.random.random(10).astype("float32")
#
# mul_out0 = np.dot(x0.reshape(16, 4 * 8), w0)
# mul_out1 = np.dot(x1.reshape(4 * 4, 32), w1)
# sum_out = mul_out0 + mul_out1
# add_out = np.add(sum_out, b)
# sigmoid_out = 1 / (1 + np.exp(-add_out))
#
# self.op_type = "fc"
# self.inputs = {
# "X": [("X0", x0), ("X1", x1)],
# "W": [("W0", w0), ("W1", w1)],
# "B": b
# }
# self.attrs = {"xNumColDims": [1, 2], "activation": "sigmoid"}
# self.outputs = {
# "MulOut": [("MulOut0", mul_out0), ("MulOut1", mul_out1)],
# "SumOut": sum_out,
# "AddOut": add_out,
# "Out": sigmoid_out
# }
#
# def test_check_output(self):
# self.check_output()
#
# def test_check_grad(self):
# self.check_grad(
# ["X0", "X1", "W0", "W1", "B"], "Out", max_relative_error=0.01)
if
__name__
==
'__main__'
:
unittest
.
main
()
python/paddle/v2/framework/tests/test_identity_op.py
已删除
100644 → 0
浏览文件 @
bc151174
import
unittest
import
numpy
as
np
from
op_test
import
OpTest
class
TestIdentityOp
(
OpTest
):
def
setUp
(
self
):
self
.
op_type
=
"identity"
self
.
inputs
=
{
'X'
:
np
.
random
.
random
((
10
,
10
)).
astype
(
"float32"
)}
self
.
outputs
=
{
'Y'
:
self
.
inputs
[
'X'
]}
def
test_check_output
(
self
):
self
.
check_output
()
def
test_check_grad
(
self
):
self
.
check_grad
([
'X'
],
'Y'
)
if
__name__
==
"__main__"
:
unittest
.
main
()
python/paddle/v2/framework/tests/test_interp_op.py
已删除
100644 → 0
浏览文件 @
bc151174
import
unittest
import
numpy
as
np
from
op_test
import
OpTest
class
TestInterpOp
(
OpTest
):
def
setUp
(
self
):
self
.
op_type
=
"interp"
x
=
np
.
random
.
random
((
2
,
3
)).
astype
(
"float32"
)
y
=
np
.
random
.
random
((
2
,
3
)).
astype
(
"float32"
)
w
=
np
.
random
.
random
(
2
).
astype
(
"float32"
)
sub_out
=
x
-
y
mul_out
=
sub_out
*
w
.
reshape
(
2
,
1
)
out
=
mul_out
+
y
self
.
inputs
=
{
'X'
:
x
,
'Y'
:
y
,
'W'
:
w
}
self
.
outputs
=
{
'Out'
:
out
,
'SubOut'
:
sub_out
,
'MulOut'
:
mul_out
}
def
test_check_output
(
self
):
self
.
check_output
()
def
test_check_grad_normal
(
self
):
self
.
check_grad
([
'X'
,
'Y'
],
'Out'
)
if
__name__
==
"__main__"
:
unittest
.
main
()
python/paddle/v2/framework/tests/test_pad_op.py
浏览文件 @
db157eda
...
...
@@ -27,7 +27,7 @@ class TestPadOp(OpTest):
def
initTestCase
(
self
):
self
.
shape
=
(
16
,
16
)
self
.
paddings
=
[(
0
,
1
),
(
2
,
3
)]
self
.
pad_value
=
0
self
.
pad_value
=
0
.0
class
TestCase1
(
TestPadOp
):
...
...
@@ -41,7 +41,7 @@ class TestCase2(TestPadOp):
def
initTestCase
(
self
):
self
.
shape
=
(
2
,
2
,
2
)
self
.
paddings
=
[(
0
,
0
),
(
0
,
0
),
(
1
,
2
)]
self
.
pad_value
=
1
self
.
pad_value
=
1
.0
class
TestCase3
(
TestPadOp
):
...
...
python/paddle/v2/framework/tests/test_reduce_op.py
浏览文件 @
db157eda
...
...
@@ -85,33 +85,5 @@ class Test1DReduce(OpTest):
self
.
check_grad
([
'X'
],
'Out'
)
class
TestNorm
(
OpTest
):
def
setUp
(
self
):
# use x away from 0 to avoid errors of numerical gradient when gradient near 0
x
=
np
.
random
.
random
((
5
,
6
,
10
)).
astype
(
"float32"
)
+
0.2
p
=
2
dim
=
1
keep_dim
=
False
abs_out
=
np
.
absolute
(
x
)
pow_out
=
np
.
power
(
x
,
p
)
sum_out
=
np
.
sum
(
pow_out
,
axis
=
dim
,
keepdims
=
keep_dim
)
out
=
np
.
power
(
sum_out
,
1.
/
p
)
self
.
op_type
=
"norm"
self
.
inputs
=
{
'X'
:
x
}
self
.
attrs
=
{
"p"
:
p
,
"dim"
:
dim
,
"keep_dim"
:
keep_dim
}
self
.
outputs
=
{
"AbsOut"
:
abs_out
,
"PowOut"
:
pow_out
,
"SumOut"
:
sum_out
,
"Out"
:
out
}
def
test_check_output
(
self
):
self
.
check_output
()
def
test_check_grad
(
self
):
self
.
check_grad
([
'X'
],
'Out'
,
max_relative_error
=
0.01
)
if
__name__
==
'__main__'
:
unittest
.
main
()
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录